Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into child-rendering-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
danielnaab committed Jan 10, 2025
2 parents a05cfbb + 867e209 commit b7f28ac
Show file tree
Hide file tree
Showing 48 changed files with 421 additions and 35 deletions.
6 changes: 6 additions & 0 deletions packages/auth/src/context/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
import { LoginGov } from '../provider.js';
import { type AuthRepository } from '../repository/index.js';

/**
* The `BaseAuthContext` class implements the `AuthServiceContext` interface,
* providing an authentication context for user sessions and database interactions.
* It integrates with a repository for database operations, a third-party login provider,
* and various utilities for managing cookies and user sessions.
*/
export class BaseAuthContext implements AuthServiceContext {
private lucia?: Lucia;

Expand Down
16 changes: 12 additions & 4 deletions packages/auth/src/lucia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ import { Lucia } from 'lucia';

import { type Database } from '@atj/database';

/**
* Factory function to create a SQLite Lucia adapter.
*
* @param {Sqlite3Database} db - The SQLite3 database instance used to initialize the adapter.
*/
export const createSqliteLuciaAdapter = (db: Sqlite3Database) => {
const adapter = new BetterSqlite3Adapter(db, {
return new BetterSqlite3Adapter(db, {
user: 'users',
session: 'sessions',
});
return adapter;
};

/**
* Factory function to create a Postgres Lucia adapter.
*
* @param {Sqlite3Database} pgPool - The SQLite3 database instance used to initialize the adapter.
*/
export const createPostgresLuciaAdapter = (pgPool: any) => {
const adapter = new NodePostgresAdapter(pgPool, {
return new NodePostgresAdapter(pgPool, {
user: 'users',
session: 'sessions',
});
return adapter;
};

declare module 'lucia' {
Expand Down
5 changes: 5 additions & 0 deletions packages/auth/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export type LoginGovOptions = {
redirectURI?: string;
};

/**
* The LoginGov class implements the OAuth2ProviderWithPKCE interface
* and provides functionality to authenticate users using Login.gov's
* OAuth 2.0 with PKCE flow.
*/
export class LoginGov implements OAuth2ProviderWithPKCE {
private client: OAuth2Client;
//private clientSecret: string;
Expand Down
7 changes: 6 additions & 1 deletion packages/auth/src/repository/create-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ type Session = {
userId: string;
};

/**
* Asynchronously creates a new session in the database.
*
* @param {DatabaseContext} ctx - The database context used to manage the connection and provide access to the database engine.
* @param {Session} session - The session data to be inserted, containing properties such as id, expiration time, session token, and user id.
*/
export const createSession = async (ctx: DatabaseContext, session: Session) => {
const db = await ctx.getKysely();
const result = await db.transaction().execute(async trx => {
Expand All @@ -17,7 +23,6 @@ export const createSession = async (ctx: DatabaseContext, session: Session) => {
expires_at: dateValue(ctx.engine, session.expiresAt),
session_token: session.sessionToken,
user_id: session.userId,
//...session.attributes,
})
.execute();
});
Expand Down
6 changes: 6 additions & 0 deletions packages/auth/src/repository/create-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { randomUUID } from 'crypto';

import { type DatabaseContext } from '@atj/database';

/**
* Asynchronously creates a new user record in the database.
*
* @param {DatabaseContext} ctx - The database context used to interact with the database.
* @param {string} email - The email address of the user to be created.
*/
export const createUser = async (ctx: DatabaseContext, email: string) => {
const id = randomUUID();

Expand Down
6 changes: 6 additions & 0 deletions packages/auth/src/repository/get-user-id.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { type DatabaseContext } from '@atj/database';

/**
* Retrieves the unique identifier (ID) of a user based on their email address.
*
* @param {DatabaseContext} ctx - The database context used to establish a connection and perform the query.
* @param {string} email - The email address of the user whose ID is to be retrieved.
*/
export const getUserId = async (ctx: DatabaseContext, email: string) => {
const db = await ctx.getKysely();
const user = await db.transaction().execute(trx => {
Expand Down
11 changes: 11 additions & 0 deletions packages/auth/src/repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import { createSession } from './create-session.js';
import { createUser } from './create-user.js';
import { getUserId } from './get-user-id.js';

/**
* Factory function to create an authentication repository.
*
* @param {DatabaseContext} ctx - The database context used for initializing the repository.
* @returns Returns an authentication service object with methods to handle user authentication and management.
*
* The returned service object provides the following methods:
* - `createSession`
* - `createUser`
* - `getUserId`
*/
export const createAuthRepository = (ctx: DatabaseContext) =>
createService(ctx, {
createSession,
Expand Down
8 changes: 8 additions & 0 deletions packages/auth/src/services/get-provider-redirect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { generateCodeVerifier, generateState } from 'arctic';
import { type AuthServiceContext } from './index.js';

/**
* Asynchronously generates a redirection URL for an OAuth authorization request and associated cookies.
*
* This function interacts with the provided authentication service context to create an authorization
* URL, including necessary state and verification parameters for security measures.
*
* @param {AuthServiceContext} ctx - The authentication service context used to interact with the provider.
*/
export const getProviderRedirect = async (ctx: AuthServiceContext) => {
const state = generateState();
const codeVerifier = generateCodeVerifier();
Expand Down
5 changes: 5 additions & 0 deletions packages/auth/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export type AuthServiceContext = {
isUserAuthorized: (email: string) => Promise<boolean>;
};

/**
* Factory function to create an authentication service.
*
* @param {AuthServiceContext} ctx - The context required to initialize the authentication service.
*/
export const createAuthService = (ctx: AuthServiceContext) =>
createService(ctx, {
getProviderRedirect,
Expand Down
9 changes: 7 additions & 2 deletions packages/auth/src/services/logout.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { type Session } from 'lucia';
import { type AuthServiceContext } from './index.js';

/**
* Logs out a user by invalidating their existing session and creating a blank session cookie.
*
* @param {AuthServiceContext} ctx - The authentication service context.
* @param {Session} session - The session object to be invalidated.
*/
export const logOut = async (ctx: AuthServiceContext, session: Session) => {
const lucia = await ctx.getLucia();
await lucia.invalidateSession(session.id);
const sessionCookie = lucia.createBlankSessionCookie();
return sessionCookie;
return lucia.createBlankSessionCookie();
};
14 changes: 14 additions & 0 deletions packages/auth/src/services/process-provider-callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ type Params = {
state?: string | null;
};

/**
* Processes the provider callback for an OAuth-based authentication flow. Ensures data integrity and security through checks like nonce matching,
* state validation, and proper error handling.
*
* @param {AuthServiceContext} ctx - The context object containing authentication services, database access, and related utilities.
* @param {Params} params - The parameters received from the provider as part of the callback request.
* @param {Params & { nonce: string | null }} storedParams - Locally stored parameters including the nonce for verification and validation.
* @param {function} [fetchUserData=fetchUserDataImpl] - A function to fetch user data from the external provider using the access token.
*/
export const processProviderCallback = async (
ctx: AuthServiceContext,
params: Params,
Expand Down Expand Up @@ -91,6 +100,11 @@ export const processProviderCallback = async (
});
};

/**
* Fetches user data from the login-provider endpoint using the provided access token with bearer token auth.
*
* @param {string} accessToken - The access token to authorize the request.
*/
const fetchUserDataImpl = (accessToken: string) =>
fetch('https://idp.int.identitysandbox.gov/api/openid_connect/userinfo', {
headers: {
Expand Down
10 changes: 10 additions & 0 deletions packages/auth/src/services/process-session-cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import { type VoidResult } from '@atj/common';

import { type AuthServiceContext } from './index.js';

/**
* Processes the session cookie in the context of an authentication request.
* This function validates the session cookie from the incoming request and
* sets the appropriate user session in the given context. It checks the
* request's origin for security and handles session expiration or invalidation
* by creating blank session cookies when necessary.
*
* @param {AuthServiceContext} ctx - The authentication service context used to manage sessions and cookies.
* @param {Request} request - The incoming HTTP request object containing session and origin information.
*/
export const processSessionCookie = async (
ctx: AuthServiceContext,
request: Request
Expand Down
18 changes: 12 additions & 6 deletions packages/database/src/clients/knex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ const migrationsDirectory = path.resolve(
'../../migrations'
);

export const createKnex = (config: Knex.Config): Knex => knex(config);

/**
* Creates and configures a query builder instance configured for a PostgreSQL database.
*
* @param {string} connectionString - The connection string for the PostgreSQL database.
* @param {boolean} [ssl=false] - Indicates whether SSL should be enabled for the connection.
* If enabled, `rejectUnauthorized` will be set to `false` to allow self-signed certificates.
*/
export const getPostgresKnex = (
connectionString: string,
ssl: boolean = false
Expand All @@ -33,10 +38,11 @@ export const getInMemoryKnex = (): Knex => {
return getSqlite3Knex(':memory:');
};

export const getFileSystemKnex = (path: string): Knex => {
return getSqlite3Knex(path);
};

/**
* Creates and configures a query builder instance configured for a SQLite database.
*
* @param {string} filename - The path to the SQLite database file.
*/
const getSqlite3Knex = (filename: string): Knex => {
return knex({
client: 'better-sqlite3',
Expand Down
7 changes: 7 additions & 0 deletions packages/database/src/clients/kysely/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import pg from 'pg';

import { type Database } from './types.js';

/**
* Creates a new Postgres database connection.
*
* @param {string} connectionString - The connection string to connect to the Postgres database.
* @param {boolean} ssl - A boolean indicating whether SSL should be used for the connection.
* If true, SSL is enabled with the option to not reject unauthorized certificates.
*/
export const createPostgresDatabase = (
connectionString: string,
ssl: boolean
Expand Down
26 changes: 6 additions & 20 deletions packages/database/src/clients/kysely/sqlite3.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { Kysely, SqliteDialect } from 'kysely';
import BetterSqliteDatabase, {
type Database as SqliteDatabase,
} from 'better-sqlite3';
import { type Database as SqliteDatabase } from 'better-sqlite3';

import { type Database } from './types.js';

type TestDatabase = {
kysely: Kysely<Database>;
sqlite: SqliteDatabase;
};

export const createInMemoryDatabase = (): TestDatabase => {
const database = new BetterSqliteDatabase(':memory:');
return {
kysely: new Kysely<Database>({
dialect: new SqliteDialect({
database,
}),
}),
sqlite: database,
};
};

/**
* Creates a query builder instance configured to use SQLite as the database dialect.
*
* @param {SqliteDatabase} database - The SQLite database connection or configuration object.
*/
export const createSqliteDatabase = (database: SqliteDatabase) => {
return new Kysely<Database>({
dialect: new SqliteDialect({
Expand Down
3 changes: 3 additions & 0 deletions packages/database/src/context/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const migrationsDirectory = path.resolve(
'../../migrations'
);

/**
* Provides a context for accessing and managing a SQLite database stored on the filesystem.
*/
export class FilesystemDatabaseContext implements DatabaseContext {
public readonly engine = 'sqlite';
knex?: Knex;
Expand Down
5 changes: 5 additions & 0 deletions packages/database/src/context/in-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import { migrateDatabase } from '../management/migrate-database.js';

import { type DatabaseContext } from './types.js';

/**
* Provides a context for accessing and managing a SQLite database stored in memory.
*
* This context is implemented for testing or temporary use cases where persistence is not required.
*/
export class InMemoryDatabaseContext implements DatabaseContext {
public readonly engine = 'sqlite';
knex?: Knex;
Expand Down
3 changes: 3 additions & 0 deletions packages/database/src/context/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { migrateDatabase } from '../management/migrate-database.js';
import { type DatabaseContext } from './types.js';
import { Pool } from 'pg';

/**
* Provides a context for accessing and managing a PostgreSQL database.
*/
export class PostgresDatabaseContext implements DatabaseContext {
public readonly engine = 'postgres';
knex?: Knex;
Expand Down
11 changes: 11 additions & 0 deletions packages/database/src/management/migrate-database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { type DatabaseContext } from '../context/types.js';

/**
* Handles database migration operations.
*
* Executes the latest database migrations for the given context and
* returns a function to roll back these migrations if needed.
*
* @param {DatabaseContext} ctx - The database context providing access to
* the database instance and migration utilities.
*
* @returns {Function} A function to roll back the last applied migrations.
*/
export const migrateDatabase = async (ctx: DatabaseContext) => {
const db = await ctx.getKnex();
await db.migrate.latest();
Expand Down
Loading

0 comments on commit b7f28ac

Please sign in to comment.