Merge branch 'add-base-and-storage' into 3-add-routing

This commit is contained in:
2026-08-31 12:37:36 +00:00
5 changed files with 37 additions and 4 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
"scripts": { "scripts": {
"analyze": "tsdown --clean --no-fixed-extension --sourcemap source/index.ts && esbuild-analyzer dist/", "analyze": "tsdown --clean --no-fixed-extension --sourcemap source/index.ts && esbuild-analyzer dist/",
"build": "tsdown --clean --sourcemap source/index.ts", "build": "tsdown --clean --sourcemap source/index.ts",
"dev": "tsx watch source/app.ts", "dev": "tsx watch source/index.ts",
"docs": "typedoc --hideGenerator --categorizeByGroup", "docs": "typedoc --hideGenerator --categorizeByGroup",
"format": "prettier --write . && eslint --fix", "format": "prettier --write . && eslint --fix",
"spellcheck": "cspell 'source/**' 'test/**' 'playground/**'", "spellcheck": "cspell 'source/**' 'test/**' 'playground/**'",
+7 -1
View File
@@ -32,7 +32,7 @@ const configSchema = z.object({
.object({ .object({
origin: z.string().default('*'), origin: z.string().default('*'),
methods: z.array(z.string()).default([ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS' ]), methods: z.array(z.string()).default([ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS' ]),
allowedHeaders: z.array(z.string()).default([ 'Content-Type', 'cache-control', 'X-Timestamp', 'X-PublicKey', 'X-Signature' ]), allowedHeaders: z.array(z.string()).default([ 'Content-Type', 'cache-control', 'X-Timestamp', 'X-Public-Key', 'X-Signature' ]),
}) })
.partial() .partial()
.prefault({}), .prefault({}),
@@ -48,6 +48,11 @@ const configSchema = z.object({
.int() .int()
.positive() .positive()
.default(5 * 60 * 1000), .default(5 * 60 * 1000),
uniqueRequestCleanupIntervalMs: z.coerce
.number()
.int()
.positive()
.default(10 * 60 * 1000),
}) })
.prefault({}), .prefault({}),
}); });
@@ -83,6 +88,7 @@ export class Config {
}, },
auth: { auth: {
timestampWindowMs: process.env.AUTH_TIMESTAMP_WINDOW_MS ? Number(process.env.AUTH_TIMESTAMP_WINDOW_MS) : undefined, timestampWindowMs: process.env.AUTH_TIMESTAMP_WINDOW_MS ? Number(process.env.AUTH_TIMESTAMP_WINDOW_MS) : undefined,
uniqueRequestCleanupIntervalMs: process.env.AUTH_UNIQUE_REQUEST_CLEANUP_INTERVAL_MS ? Number(process.env.AUTH_UNIQUE_REQUEST_CLEANUP_INTERVAL_MS) : undefined,
}, },
}); });
} }
@@ -23,8 +23,19 @@ export const up = async (db: Kysely<DatabaseTables>): Promise<void> => {
.addColumn('public_key', 'text', (col) => col.notNull()) .addColumn('public_key', 'text', (col) => col.notNull())
.addColumn('blob', 'blob', (col) => col.notNull()) .addColumn('blob', 'blob', (col) => col.notNull())
.addColumn('timestamp', 'integer', (col) => col.notNull().defaultTo(millisecondTime)) .addColumn('timestamp', 'integer', (col) => col.notNull().defaultTo(millisecondTime))
.addColumn('signature', 'text', (col) => col.notNull())
.addPrimaryKeyConstraint('pk_resource_data', [ 'resource_id', 'public_key' ]) .addPrimaryKeyConstraint('pk_resource_data', [ 'resource_id', 'public_key' ])
.execute(); .execute();
// Table for authed requests
// We will store the signature and the timestamp of the request, and we will clear out rows that are older than our msTimeout for our auth
await db.schema
.createTable('authed_requests')
.ifNotExists()
.addColumn('signature', 'text', (col) => col.notNull())
.addColumn('timestamp', 'integer', (col) => col.notNull().defaultTo(millisecondTime))
.addPrimaryKeyConstraint('pk_authed_requests', [ 'signature' ])
.execute();
}; };
/** /**
@@ -35,4 +46,7 @@ export const up = async (db: Kysely<DatabaseTables>): Promise<void> => {
export const down = async (db: Kysely<DatabaseTables>): Promise<void> => { export const down = async (db: Kysely<DatabaseTables>): Promise<void> => {
await db.schema.dropTable('resource_data').ifExists() await db.schema.dropTable('resource_data').ifExists()
.execute(); .execute();
await db.schema.dropTable('authed_requests').ifExists()
.execute();
}; };
+13
View File
@@ -22,9 +22,22 @@ export interface ResourceDataTable {
/** Millisecond timestamp of the last write. */ /** Millisecond timestamp of the last write. */
timestamp: Timestamp; timestamp: Timestamp;
/** Signature of the write. */
signature: string;
}
export interface AuthedRequestsTable {
/** Signature of the request. */
signature: string;
/** Millisecond timestamp of the request. */
timestamp: Timestamp;
} }
/** Complete Kysely schema mapping for the sync server database. */ /** Complete Kysely schema mapping for the sync server database. */
export interface DatabaseTables { export interface DatabaseTables {
resource_data: ResourceDataTable; resource_data: ResourceDataTable;
authed_requests: AuthedRequestsTable;
} }
+1 -1
View File
@@ -18,7 +18,7 @@ const testConfigDefaultsTo1MiBRequestBodyLimit = (): void => {
expect(config.server.host).toBe('0.0.0.0'); expect(config.server.host).toBe('0.0.0.0');
expect(config.server.cors.origin).toBe('*'); expect(config.server.cors.origin).toBe('*');
expect(config.server.cors.methods).toEqual([ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS' ]); expect(config.server.cors.methods).toEqual([ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS' ]);
expect(config.server.cors.allowedHeaders).toEqual([ 'Content-Type', 'cache-control', 'X-Timestamp', 'X-PublicKey', 'X-Signature' ]); expect(config.server.cors.allowedHeaders).toEqual([ 'Content-Type', 'cache-control', 'X-Timestamp', 'X-Public-Key', 'X-Signature' ]);
expect(config.auth.timestampWindowMs).toBe(300000); expect(config.auth.timestampWindowMs).toBe(300000);
}; };