From 947b2db347c02b2e90d9ef5981d637a3e53fd566 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 6 Aug 2024 11:09:36 -0500 Subject: [PATCH] Manage database dependencies so the integrating form service apps may import and run the server's express handler. This includes: - Create helper in @atj/common to create service objects from a collection of service functions - Remove module-global imports of @atj/database from the @atj/server, replacing with async imports where appropriate TODO: The app integration tests for Kansas and DOJ are now passing, but the build bundle fails. --- apps/server-doj/src/server.ts | 25 +++++++- apps/server-kansas/package.json | 1 + apps/server-kansas/src/server.ts | 25 +++++++- packages/auth/package.json | 7 ++- packages/auth/src/context/dev.ts | 6 +- packages/auth/src/context/test.ts | 10 +-- packages/auth/src/index.ts | 8 +-- packages/auth/src/lucia.ts | 31 +++++++++- .../process-provider-callback.test.ts | 3 +- .../src/services/process-provider-callback.ts | 5 +- .../services/process-session-cookie.test.ts | 6 +- packages/auth/tsconfig.json | 6 +- packages/common/src/index.ts | 2 + packages/common/src/service.ts | 44 +++++++++++++ packages/database/build.js | 1 + packages/database/package.json | 7 ++- .../database/src/clients/kysely/sqlite3.ts | 2 +- packages/database/src/context/dev.ts | 4 +- packages/database/src/context/test.ts | 4 +- packages/database/src/gateways/index.ts | 3 - .../src/gateways/sessions/create-session.ts | 3 - packages/database/src/index.ts | 29 +++++---- packages/database/src/management/index.ts | 1 - packages/database/tsconfig.json | 7 ++- packages/server/auth.config.ts | 31 ---------- packages/server/build.js | 16 ----- packages/server/{src => }/handler.ts | 31 ++++++++-- packages/server/package.json | 11 ++-- packages/server/src/context.ts | 61 ++++++++++--------- packages/server/src/middleware.ts | 3 +- packages/server/tsconfig.build.json | 3 +- pnpm-lock.yaml | 12 ++-- tsconfig.base.json | 1 + tsconfig.json | 2 + 34 files changed, 263 insertions(+), 148 deletions(-) create mode 100644 packages/common/src/service.ts delete mode 100644 packages/database/src/gateways/index.ts delete mode 100644 packages/database/src/management/index.ts delete mode 100644 packages/server/auth.config.ts delete mode 100644 packages/server/build.js rename packages/server/{src => }/handler.ts (50%) diff --git a/apps/server-doj/src/server.ts b/apps/server-doj/src/server.ts index 7ef19314..40fd2175 100644 --- a/apps/server-doj/src/server.ts +++ b/apps/server-doj/src/server.ts @@ -1,7 +1,28 @@ -import { createServer } from '@atj/server/dist/handler.js'; +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const getDirname = () => dirname(fileURLToPath(import.meta.url)); + +export const createCustomServer = async (): Promise => { + const { createDevDatabaseContext, createDatabaseService } = await import( + '@atj/database' + ); + const { createServer } = await import('@atj/server'); + + const dbCtx = await createDevDatabaseContext( + path.join(getDirname(), '../doj.db') + ); + const database = createDatabaseService(dbCtx); -export const createCustomServer = () => { return createServer({ title: 'DOJ Form Service', + database, + loginGovOptions: { + loginGovUrl: 'https://idp.int.identitysandbox.gov', + clientId: + 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', + clientSecret: '', + redirectURI: 'http://localhost:4322/signin/callback', + }, }); }; diff --git a/apps/server-kansas/package.json b/apps/server-kansas/package.json index 57692999..fee86a97 100644 --- a/apps/server-kansas/package.json +++ b/apps/server-kansas/package.json @@ -12,6 +12,7 @@ "test": "vitest run --coverage" }, "dependencies": { + "@atj/database": "workspace:*", "@atj/server": "workspace:*" }, "devDependencies": { diff --git a/apps/server-kansas/src/server.ts b/apps/server-kansas/src/server.ts index 87e071aa..97643f28 100644 --- a/apps/server-kansas/src/server.ts +++ b/apps/server-kansas/src/server.ts @@ -1,7 +1,28 @@ -import { createServer } from '@atj/server/dist/handler.js'; +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const getDirname = () => dirname(fileURLToPath(import.meta.url)); + +export const createCustomServer = async (): Promise => { + const { createDevDatabaseContext, createDatabaseService } = await import( + '@atj/database' + ); + const { createServer } = await import('@atj/server'); + + const dbCtx = await createDevDatabaseContext( + path.join(getDirname(), '../doj.db') + ); + const database = createDatabaseService(dbCtx); -export const createCustomServer = () => { return createServer({ title: 'KS Courts Form Service', + database, + loginGovOptions: { + loginGovUrl: 'https://idp.int.identitysandbox.gov', + clientId: + 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', + clientSecret: '', + redirectURI: 'http://localhost:4322/signin/callback', + }, }); }; diff --git a/packages/auth/package.json b/packages/auth/package.json index e35e50b4..af5046ca 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -4,10 +4,11 @@ "description": "10x ATJ auth module", "type": "module", "license": "CC0", - "main": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.js", "scripts": { - "build": "tsup src/* --env.NODE_ENV production", - "dev": "tsup src/* --watch", + "build": "tsc", + "dev": "tsc --watch", "test": "vitest run --coverage" }, "dependencies": { diff --git a/packages/auth/src/context/dev.ts b/packages/auth/src/context/dev.ts index 9caf26aa..173a1630 100644 --- a/packages/auth/src/context/dev.ts +++ b/packages/auth/src/context/dev.ts @@ -1,6 +1,6 @@ import { Cookie, Lucia } from 'lucia'; -import { type DevDatabaseContext } from '@atj/database'; +import { type DatabaseService } from '@atj/database'; import { type AuthContext, type UserSession } from '..'; import { createTestLuciaAdapter } from '../lucia'; @@ -10,7 +10,7 @@ export class DevAuthContext implements AuthContext { private lucia?: Lucia; constructor( - public database: DevDatabaseContext, + public database: DatabaseService, public provider: LoginGov, public getCookie: (name: string) => string | undefined, public setCookie: (cookie: Cookie) => void, @@ -19,7 +19,7 @@ export class DevAuthContext implements AuthContext { async getLucia() { const sqlite3Adapter = createTestLuciaAdapter( - await this.database.getSqlite3() + await (this.database.getContext() as any).getSqlite3() ); if (!this.lucia) { this.lucia = new Lucia(sqlite3Adapter, { diff --git a/packages/auth/src/context/test.ts b/packages/auth/src/context/test.ts index dada93d1..659cc32a 100644 --- a/packages/auth/src/context/test.ts +++ b/packages/auth/src/context/test.ts @@ -2,8 +2,9 @@ import { Cookie, Lucia } from 'lucia'; import { vi } from 'vitest'; import { - type TestDatabaseContext, + type DatabaseService, createTestDatabaseContext, + createDatabaseService, } from '@atj/database'; import { type AuthContext, type UserSession } from '..'; @@ -22,7 +23,8 @@ export const createTestAuthContext = async (opts?: Partial) => { setCookie: opts?.setCookie || vi.fn(), setUserSession: opts?.setUserSession || vi.fn(), }; - const database = await createTestDatabaseContext(); + const dbContext = await createTestDatabaseContext(); + const database = createDatabaseService(dbContext); return new TestAuthContext( database, new LoginGov({ @@ -42,7 +44,7 @@ export class TestAuthContext implements AuthContext { private lucia?: Lucia; constructor( - public database: TestDatabaseContext, + public database: DatabaseService, public provider: LoginGov, public getCookie: (name: string) => string | undefined, public setCookie: (cookie: Cookie) => void, @@ -50,7 +52,7 @@ export class TestAuthContext implements AuthContext { ) {} async getLucia() { - const sqlite3 = await this.database.getSqlite3(); + const sqlite3 = await (this.database.getContext() as any).getSqlite3(); const sqlite3Adapter = createTestLuciaAdapter(sqlite3); if (!this.lucia) { this.lucia = new Lucia(sqlite3Adapter, { diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index a4772614..bda86df1 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,10 +1,10 @@ import { type Cookie, type User, type Session, type Lucia } from 'lucia'; -import { type DatabaseContext } from '@atj/database'; +import { type DatabaseService } from '@atj/database'; export { DevAuthContext } from './context/dev'; -import { LoginGov } from './provider'; -export { LoginGov }; +import { type LoginGovOptions, LoginGov } from './provider'; +export { type LoginGovOptions, LoginGov }; export { getProviderRedirect } from './services/get-provider-redirect'; export { logOut } from './services/logout'; export { processProviderCallback } from './services/process-provider-callback'; @@ -17,7 +17,7 @@ export type UserSession = { }; export type AuthContext = { - database: DatabaseContext; + database: DatabaseService; provider: LoginGov; getCookie: (name: string) => string | undefined; setCookie: (cookie: Cookie) => void; diff --git a/packages/auth/src/lucia.ts b/packages/auth/src/lucia.ts index f8c08a52..4909a9c4 100644 --- a/packages/auth/src/lucia.ts +++ b/packages/auth/src/lucia.ts @@ -1,5 +1,5 @@ import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; -import { Database as Sqlite3Database } from 'better-sqlite3'; +import { type Database as Sqlite3Database } from 'better-sqlite3'; import { Lucia } from 'lucia'; import { type Database } from '@atj/database'; @@ -9,6 +9,7 @@ export const createTestLuciaAdapter = (db: Sqlite3Database) => { user: 'users', session: 'sessions', }); + //const adapter = new KyselyAdapter(); return adapter; }; @@ -18,3 +19,31 @@ declare module 'lucia' { DatabaseUserAttributes: Omit; } } + +/* +export class KyselyAdapter implements Adapter { + getSessionAndUser( + sessionId: string + ): Promise<[session: DatabaseSession | null, user: DatabaseUser | null]> { + throw new Error('Method not implemented.'); + } + getUserSessions(userId: string): Promise { + throw new Error('Method not implemented.'); + } + setSession(session: DatabaseSession): Promise { + throw new Error('Method not implemented.'); + } + updateSessionExpiration(sessionId: string, expiresAt: Date): Promise { + throw new Error('Method not implemented.'); + } + deleteSession(sessionId: string): Promise { + throw new Error('Method not implemented.'); + } + deleteUserSessions(userId: string): Promise { + throw new Error('Method not implemented.'); + } + deleteExpiredSessions(): Promise { + throw new Error('Method not implemented.'); + } +} +*/ diff --git a/packages/auth/src/services/process-provider-callback.test.ts b/packages/auth/src/services/process-provider-callback.test.ts index e29f2296..9b243c40 100644 --- a/packages/auth/src/services/process-provider-callback.test.ts +++ b/packages/auth/src/services/process-provider-callback.test.ts @@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { processProviderCallback } from './process-provider-callback'; import { createTestAuthContext } from '../context/test'; -import { createUser } from '@atj/database'; import { AuthContext } from '..'; describe('processProviderCallback', () => { @@ -14,7 +13,7 @@ describe('processProviderCallback', () => { // Create test auth context with a test user in the db ctx = await createTestAuthContext(); - const user = await createUser(ctx.database, 'fake-user@gsa.com'); + const user = await ctx.database.createUser('fake-user@gsa.com'); if (!user) { expect.fail('error creating test user'); } diff --git a/packages/auth/src/services/process-provider-callback.ts b/packages/auth/src/services/process-provider-callback.ts index 1c487e6a..54b689e8 100644 --- a/packages/auth/src/services/process-provider-callback.ts +++ b/packages/auth/src/services/process-provider-callback.ts @@ -1,7 +1,6 @@ import { OAuth2RequestError } from 'arctic'; import * as r from '@atj/common'; -import { getUserId, createUser } from '@atj/database'; import { type AuthContext } from '..'; import { randomUUID } from 'crypto'; @@ -86,9 +85,9 @@ export const processProviderCallback = async ( if (!userDataResult.success) { return userDataResult; } - let userId = await getUserId(ctx.database, userDataResult.data.email); + let userId = await ctx.database.getUserId(userDataResult.data.email); if (!userId) { - const newUser = await createUser(ctx.database, userDataResult.data.email); + const newUser = await ctx.database.createUser(userDataResult.data.email); if (!newUser) { return r.failure({ status: 500, message: 'error creating new user' }); } diff --git a/packages/auth/src/services/process-session-cookie.test.ts b/packages/auth/src/services/process-session-cookie.test.ts index 7b9a9458..e55c69cb 100644 --- a/packages/auth/src/services/process-session-cookie.test.ts +++ b/packages/auth/src/services/process-session-cookie.test.ts @@ -1,8 +1,6 @@ import { randomUUID } from 'crypto'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { createSession, createUser } from '@atj/database'; - import { createTestAuthContext } from '../context/test'; import { processSessionCookie } from './process-session-cookie'; @@ -121,11 +119,11 @@ const setUpTest = async (sessionExpirationDate: Date) => { setUserSession: vi.fn(), }; const ctx = await createTestAuthContext(mocks); - const user = await createUser(ctx.database, 'user@test.gov'); + const user = await ctx.database.createUser('user@test.gov'); if (!user) { expect.fail('error creating test user'); } - const sessionId = await createSession(ctx.database, { + const sessionId = await ctx.database.createSession({ id: randomUUID(), expiresAt: addOneDay(sessionExpirationDate), sessionToken: 'my-token', diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json index 690985d4..24abc391 100644 --- a/packages/auth/tsconfig.json +++ b/packages/auth/tsconfig.json @@ -1,9 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "emitDeclarationOnly": false, "outDir": "./dist", - "emitDeclarationOnly": true + "rootDir": "./src" }, - "include": ["./src", "global.d.ts"], + "include": ["src", "global.d.ts"], + "exclude": ["./dist"], "references": [] } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index ad6f4016..239126d5 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,3 +1,5 @@ +export { createService } from './service'; + export type Success = { success: true; data: T }; export type VoidSuccess = { success: true }; export type Failure = { success: false; error: E }; diff --git a/packages/common/src/service.ts b/packages/common/src/service.ts new file mode 100644 index 00000000..9e96589f --- /dev/null +++ b/packages/common/src/service.ts @@ -0,0 +1,44 @@ +type ServiceFunction = ( + context: Context, + ...args: Args +) => Return; + +type ServiceFunctions = { + [key: string]: ServiceFunction; +}; + +type WithoutFirstArg = F extends (context: any, ...args: infer P) => infer R + ? (...args: P) => R + : never; + +type Service< + Context extends any, + Functions extends ServiceFunctions, +> = { + [K in keyof Functions]: WithoutFirstArg; +} & { getContext: () => Context }; + +export const createService = < + Context extends any, + Functions extends ServiceFunctions, +>( + ctx: Context, + serviceFunctions: Functions +): Service => { + const handler: ProxyHandler = { + get(target: Functions, prop: string | symbol) { + if (prop === 'getContext') { + return () => ctx; + } + const propKey = prop as keyof Functions; + const originalFn = target[propKey]; + return (...args: any[]) => + (originalFn as Function).call(null, ctx, ...args); + }, + }; + + return new Proxy(serviceFunctions, handler) as unknown as Service< + Context, + Functions + >; +}; diff --git a/packages/database/build.js b/packages/database/build.js index fbf0c78e..479ad008 100644 --- a/packages/database/build.js +++ b/packages/database/build.js @@ -5,6 +5,7 @@ esbuild bundle: false, entryPoints: ['./src/index.ts'], //external: ['sqlite3', 'knex'], + packages: 'external', format: 'esm', minify: true, outdir: './dist', diff --git a/packages/database/package.json b/packages/database/package.json index aed0993b..943c79b9 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -4,10 +4,11 @@ "description": "10x ATJ database", "type": "module", "license": "CC0", - "main": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { - "build": "tsup src/* --env.NODE_ENV production", - "dev": "tsup src/* --watch", + "build": "tsc", + "dev": "tsc --watch", "test": "vitest run --coverage" }, "dependencies": { diff --git a/packages/database/src/clients/kysely/sqlite3.ts b/packages/database/src/clients/kysely/sqlite3.ts index 2ef7e755..7151b9c7 100644 --- a/packages/database/src/clients/kysely/sqlite3.ts +++ b/packages/database/src/clients/kysely/sqlite3.ts @@ -1,6 +1,6 @@ import { Kysely, SqliteDialect } from 'kysely'; import BetterSqliteDatabase, { - Database as SqliteDatabase, + type Database as SqliteDatabase, } from 'better-sqlite3'; import { type Database } from './types'; diff --git a/packages/database/src/context/dev.ts b/packages/database/src/context/dev.ts index a81095bc..63ae0b26 100644 --- a/packages/database/src/context/dev.ts +++ b/packages/database/src/context/dev.ts @@ -1,12 +1,12 @@ import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; -import { Database as SqliteDatabase } from 'better-sqlite3'; +import { type Database as SqliteDatabase } from 'better-sqlite3'; import knex, { type Knex } from 'knex'; import { type Kysely } from 'kysely'; import { type Database, createSqliteDatabase } from '../clients/kysely'; -import { migrateDatabase } from '../management'; +import { migrateDatabase } from '../management/migrate-database'; import { type DatabaseContext } from './types'; diff --git a/packages/database/src/context/test.ts b/packages/database/src/context/test.ts index f9bbc392..0d30e15e 100644 --- a/packages/database/src/context/test.ts +++ b/packages/database/src/context/test.ts @@ -1,10 +1,10 @@ -import { Database as SqliteDatabase } from 'better-sqlite3'; +import { type Database as SqliteDatabase } from 'better-sqlite3'; import { type Knex } from 'knex'; import { type Kysely } from 'kysely'; import { getTestKnex } from '../clients/knex'; import { type Database, createSqliteDatabase } from '../clients/kysely'; -import { migrateDatabase } from '../management'; +import { migrateDatabase } from '../management/migrate-database'; import { type DatabaseContext } from './types'; diff --git a/packages/database/src/gateways/index.ts b/packages/database/src/gateways/index.ts deleted file mode 100644 index 54aab41b..00000000 --- a/packages/database/src/gateways/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { createSession } from './sessions/create-session'; -export { createUser } from './users/create-user'; -export { getUserId } from './users/get-user-id'; diff --git a/packages/database/src/gateways/sessions/create-session.ts b/packages/database/src/gateways/sessions/create-session.ts index 678961cf..342d2188 100644 --- a/packages/database/src/gateways/sessions/create-session.ts +++ b/packages/database/src/gateways/sessions/create-session.ts @@ -1,6 +1,3 @@ -import { randomUUID } from 'crypto'; - -import { type SessionSchema } from '../../schema'; import { type DatabaseContext } from '../../context/types'; type Session = { diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts index d0c0cc08..a17a669a 100644 --- a/packages/database/src/index.ts +++ b/packages/database/src/index.ts @@ -1,17 +1,24 @@ -export { - type Database, - type DatabaseClient, - createInMemoryDatabase, -} from './clients/kysely'; +import { createService } from '@atj/common'; + +import { DatabaseContext } from './context/types'; +import { createSession } from './gateways/sessions/create-session'; +import { createUser } from './gateways/users/create-user'; +import { getUserId } from './gateways/users/get-user-id'; + export { type DevDatabaseContext, createDevDatabaseContext, } from './context/dev'; -export { - type TestDatabaseContext, - createTestDatabaseContext, -} from './context/test'; +export { createTestDatabaseContext } from './context/test'; +export { type Database } from './clients/kysely'; export { type DatabaseContext } from './context/types'; +export { migrateDatabase } from './management/migrate-database'; + +export const createDatabaseService = (ctx: DatabaseContext) => + createService(ctx, { + createSession, + createUser, + getUserId, + }); -export * from './gateways'; -export * from './management'; +export type DatabaseService = ReturnType; diff --git a/packages/database/src/management/index.ts b/packages/database/src/management/index.ts deleted file mode 100644 index cf87b7eb..00000000 --- a/packages/database/src/management/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { migrateDatabase } from './migrate-database'; diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json index a78c2b25..f1b9b550 100644 --- a/packages/database/tsconfig.json +++ b/packages/database/tsconfig.json @@ -1,10 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "emitDeclarationOnly": true, - "outDir": "./dist" + "emitDeclarationOnly": false, + "outDir": "./dist", + "rootDir": "./src" }, - "include": ["./src/**/*"], + "include": ["src"], "exclude": ["./dist"], "references": [] } diff --git a/packages/server/auth.config.ts b/packages/server/auth.config.ts deleted file mode 100644 index 8600e86a..00000000 --- a/packages/server/auth.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { defineConfig } from 'auth-astro'; - -import { getServerSecrets } from './src/secrets'; - -const secrets = getServerSecrets((import.meta as any).env); - -export default defineConfig({ - secret: secrets.authSecret, - providers: [ - /** - * https://dashboard.int.identitysandbox.gov/service_providers/5237 - * urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj - */ - { - id: 'login-gov', - name: 'Login.gov', - type: 'oidc', - checks: ['pkce', 'state', 'nonce'], - authorization: { - params: { - //scope: 'profile openid email address phone', - acr_values: ['http://idmanagement.gov/ns/assurance/ial/1'], - }, - }, - issuer: 'https://idp.int.identitysandbox.gov', - clientId: - 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', - clientSecret: secrets.loginGov.clientSecret, - }, - ], -}); diff --git a/packages/server/build.js b/packages/server/build.js deleted file mode 100644 index bf08254b..00000000 --- a/packages/server/build.js +++ /dev/null @@ -1,16 +0,0 @@ -import esbuild from 'esbuild'; - -esbuild - .build({ - bundle: true, - entryPoints: ['./src/handler.ts'], - format: 'esm', - minify: true, - outdir: './dist', - packages: 'external', - platform: 'node', - sourcemap: true, - target: 'esnext', - tsconfig: './tsconfig.build.json', - }) - .catch(() => process.exit(1)); diff --git a/packages/server/src/handler.ts b/packages/server/handler.ts similarity index 50% rename from packages/server/src/handler.ts rename to packages/server/handler.ts index 87589910..23e620f3 100644 --- a/packages/server/src/handler.ts +++ b/packages/server/handler.ts @@ -8,7 +8,30 @@ import { fileURLToPath } from 'url'; import express from 'express'; -import { type ServerOptions } from './context'; +import { type LoginGovOptions } from '@atj/auth'; +import { type DevDatabaseContext } from '@atj/database'; + +import { type ServerOptions } from './src/context'; + +export const createServerAuth = async ({ + database, + loginGovOptions, +}: { + database: DevDatabaseContext; + loginGovOptions: LoginGovOptions; +}) => { + return createServer({ + title: 'DOJ Form Service', + database, + loginGovOptions: { + loginGovUrl: 'https://idp.int.identitysandbox.gov', + clientId: + 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', + clientSecret: '', + redirectURI: 'http://localhost:4322/signin/callback', + }, + }); +}; export const createServer = async ( serverOptions: ServerOptions @@ -22,9 +45,9 @@ export const createServer = async ( serverOptions, session: null, user: null, - } satisfies App.Locals); + }); }); - app.use(express.static(path.join(getDirname(), '../dist/client'))); + app.use(express.static(path.join(getDirname(), './dist/client'))); return app; }; @@ -36,6 +59,6 @@ const getDirname = () => { const getHandler = async () => { // @ts-ignore - const { handler } = await import('../dist/server/entry.mjs'); + const { handler } = await import('./dist/server/entry.mjs'); return handler; }; diff --git a/packages/server/package.json b/packages/server/package.json index d4bcb370..087ca323 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -2,13 +2,16 @@ "name": "@atj/server", "type": "module", "version": "0.0.1", - "main": "dist/server/index.js", + "main": "handler.ts", + "types": "handler.ts", + "files": [ + "dist", + "handler.ts" + ], "scripts": { "dev": "astro dev", "start": "astro dev", - "build": "astro check && astro build && pnpm build:entry", - "build:entry": "node ./build.js && tsc --project ./tsconfig.build.json", - "build:entry:tsup": "tsup src/index.ts --env.NODE_ENV production", + "build": "astro check && astro build", "preview": "astro preview", "astro": "astro" }, diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 426f2382..0ba85892 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -3,17 +3,15 @@ import { fileURLToPath } from 'url'; import { type APIContext, type AstroGlobal } from 'astro'; -import { type AuthContext, LoginGov, DevAuthContext } from '@atj/auth'; -import { type DevDatabaseContext, type DatabaseContext } from '@atj/database'; +import type { AuthContext, LoginGovOptions } from '@atj/auth'; +import { type DatabaseService } from '@atj/database'; import { type FormConfig, defaultFormConfig, service } from '@atj/forms'; import { type GithubRepository } from './lib/github'; -import { getServerSecrets } from './secrets'; export type AppContext = { auth: AuthContext; baseUrl: `${string}/`; - database: DatabaseContext; formConfig: FormConfig; formService: service.FormService; github: GithubRepository; @@ -32,17 +30,14 @@ const createAstroAppContext = async ( Astro: AstroGlobal | APIContext, env: any ): Promise => { - const database = await createDefaultDatabaseContext(); - const secrets = getServerSecrets(env); - const serverOptions = getServerOptions(Astro); + const serverOptions = await getServerOptions(Astro); return { - auth: createAuthContext({ + auth: await createDefaultAuthContext({ Astro, - database: database as DevDatabaseContext, - loginGovSecret: secrets.loginGov.clientSecret, + database: serverOptions.database, + loginGovOptions: serverOptions.loginGovOptions, }), baseUrl: env.BASE_URL, - database, formConfig: defaultFormConfig, formService: service.createTestFormService(), github: env.GITHUB, @@ -53,41 +48,51 @@ const createAstroAppContext = async ( export type ServerOptions = { title: string; + database: DatabaseService; + loginGovOptions: LoginGovOptions; }; -const defaultServerOptions: ServerOptions = { - title: 'Form Service', +const getDefaultServerOptions = async (): Promise => { + const database = await createDefaultDatabaseService(); + return { + title: 'Form Service', + database, + loginGovOptions: { + loginGovUrl: 'https://idp.int.identitysandbox.gov', + clientId: + 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', + clientSecret: '', + redirectURI: 'http://localhost:4322/signin/callback', + }, + }; }; -const getServerOptions = (Astro: AstroGlobal | APIContext) => { - return Astro.locals.serverOptions || defaultServerOptions; +const getServerOptions = async (Astro: AstroGlobal | APIContext) => { + return Astro.locals.serverOptions || (await getDefaultServerOptions()); }; const getDirname = () => dirname(fileURLToPath(import.meta.url)); -const createDefaultDatabaseContext = async (): Promise => { +const createDefaultDatabaseService = async () => { const { createDevDatabaseContext } = await import('@atj/database'); - return createDevDatabaseContext(join(getDirname(), 'main.db')); + const { createDatabaseService } = await import('@atj/database'); + const ctx = await createDevDatabaseContext(join(getDirname(), 'main.db')); + return createDatabaseService(ctx); }; -const createAuthContext = ({ +const createDefaultAuthContext = async ({ Astro, database, - loginGovSecret, + loginGovOptions, }: { Astro: AstroGlobal | APIContext; - database: DevDatabaseContext; - loginGovSecret: string; + database: DatabaseService; + loginGovOptions: LoginGovOptions; }) => { + const { LoginGov, DevAuthContext } = await import('@atj/auth'); return new DevAuthContext( database, - new LoginGov({ - loginGovUrl: 'https://idp.int.identitysandbox.gov', - clientId: - 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj', - clientSecret: loginGovSecret, - redirectURI: 'http://localhost:4322/signin/callback', - }), + new LoginGov(loginGovOptions), function getCookie(name: string) { return Astro.cookies.get(name)?.value; }, diff --git a/packages/server/src/middleware.ts b/packages/server/src/middleware.ts index 5530e90e..c9b4cbdb 100644 --- a/packages/server/src/middleware.ts +++ b/packages/server/src/middleware.ts @@ -1,8 +1,9 @@ import { defineMiddleware } from 'astro/middleware'; -import { getAstroAppContext } from './context'; import { processSessionCookie } from '@atj/auth'; +import { getAstroAppContext } from './context'; + export const onRequest = defineMiddleware(async (context, next) => { const { auth } = await getAstroAppContext(context); const result = await processSessionCookie(auth, context.request); diff --git a/packages/server/tsconfig.build.json b/packages/server/tsconfig.build.json index e2d7f171..33ed4b8f 100644 --- a/packages/server/tsconfig.build.json +++ b/packages/server/tsconfig.build.json @@ -9,7 +9,8 @@ "./src/env.d.ts", "./src/index.ts", "./src/context.ts", - "./src/lib/github.ts" + "./src/secrets.ts", + "./src/lib/github.ts", ], "exclude": ["./dist"], "references": [] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ccff70a..894e35e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,9 @@ importers: apps/server-kansas: dependencies: + '@atj/database': + specifier: workspace:* + version: link:../../packages/database '@atj/server': specifier: workspace:* version: link:../../packages/server @@ -12481,7 +12484,7 @@ packages: dependencies: semver: 7.6.2 shelljs: 0.8.5 - typescript: 5.6.0-dev.20240804 + typescript: 5.6.0-dev.20240806 dev: false /dset@3.1.3: @@ -14167,6 +14170,7 @@ packages: /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported requiresBuild: true dependencies: fs.realpath: 1.0.0 @@ -15705,7 +15709,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4 + debug: 4.3.5 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -22283,8 +22287,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.6.0-dev.20240804: - resolution: {integrity: sha512-ceiGncJ10hB9IgD3BxGc5e9Ug+4a/JVQKZFQR+ctQGzktWKH/vUZZNwg+PadUipPk/EH4WLFUc7infEUCNOtLQ==} + /typescript@5.6.0-dev.20240806: + resolution: {integrity: sha512-Z1wBRhFTpBIiu3jcsAkMBdefugM+kECHRO22B9Ai6D1CiHZjdvE+QLDThIoPbGFHvsEyR4gP8/0pv93QKPtcJQ==} engines: {node: '>=14.17'} hasBin: true dev: false diff --git a/tsconfig.base.json b/tsconfig.base.json index 881e9d92..b296bbe4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -3,6 +3,7 @@ "allowSyntheticDefaultImports": true, "composite": true, "declaration": true, + "declarationMap": true, "esModuleInterop": true, "isolatedModules": true, "module": "ESNext", diff --git a/tsconfig.json b/tsconfig.json index 253c251d..f3a46490 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,9 @@ { "path": "./apps/rest-api" }, { "path": "./infra/cdktf" }, { "path": "./infra/core" }, + { "path": "./packages/auth" }, { "path": "./packages/common" }, + { "path": "./packages/database" }, { "path": "./packages/dependency-graph" }, { "path": "./packages/design/tsconfig.build.json" }, { "path": "./packages/forms" },