Skip to content

Commit

Permalink
add CSP (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
quisido committed May 12, 2024
1 parent 23ef69e commit e334dee
Show file tree
Hide file tree
Showing 357 changed files with 1,850 additions and 5,957 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This repository intends to support Visual Studio Code as its primary development
environment. Its configurations in the
[`.vscode` directory](https://github.com/quisido/quisi.do/tree/main/.vscode)
are committed, and an official Code Workspace for the repository can be found in
[`quisi.do.code-workspace`](https://github.com/quisido/quisi.do/blob/main/quisi.do.code-workspace).
[`quisido.code-workspace`](https://github.com/quisido/quisi.do/blob/main/quisido.code-workspace).

## Contributing

Expand All @@ -22,3 +22,7 @@ commands in order:
`set FIND_VISUALSTUDIO_PATH=%CD%\scripts\find-visualstudio.cs&&yarn install`
to synchronize Yarn's version with its dependencies.
- `yarn run up` to upgrade dependencies.

## Commands

- `yarn run dev` to run the application and its dependencies.
1 change: 1 addition & 0 deletions packages/authn-shared/src/error-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export enum ErrorCode {
InvalidCause = 3,
InvalidDatabase = 47,
InvalidIsolateEnvironment = 6,
InvalidUsageDataset = 50,
InvalidUserId = 48,
MissingAuthnUserIdsNamespace = 49,
MissingDatabase = 46,
Expand Down
2 changes: 2 additions & 0 deletions packages/authn/.dev.vars
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
AUTHN_DB_TURSO_AUTH_TOKEN=
AUTHN_DB_TURSO_URL=
COOKIE_DOMAIN=localhost
ENVIRONMENT_NAME=development
HOST=localhost:3000
Expand Down
9 changes: 8 additions & 1 deletion packages/authn/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default [
{
files: ['src/features/handle-fetch.ts', 'src/features/state.ts'],
rules: {
'max-params': ['error', { max: 4 }],
'max-params': ['error', { max: 5 }],
},
},

Expand All @@ -48,6 +48,13 @@ export default [
},
},

{
files: ['src/test/fetch.ts'],
rules: {
'max-lines-per-function': 'off',
},
},

{
files: ['src/utils/map-readable-stream-to-string.ts'],
rules: {
Expand Down
13 changes: 3 additions & 10 deletions packages/authn/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirname, join } from 'path';
import { dirname, join } from 'node:path';
import type { JestConfigWithTsJest } from 'ts-jest';

export default {
Expand All @@ -20,15 +20,6 @@ export default {
['text', { skipEmpty: true, skipFull: true }],
],

coverageThreshold: {
global: {
branches: 32,
functions: 53,
lines: 73,
statements: 73,
},
},

moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
'proposal-async-context/src/index.js': join(
Expand All @@ -40,9 +31,11 @@ export default {

transform: {
'@quisido/authn-shared': '@monorepo-template/jest-transformer',
'@quisido/workers-shared': '@monorepo-template/jest-transformer',
'authn-shared': '@monorepo-template/jest-transformer',
fmrs: '@monorepo-template/jest-transformer',
unknown2string: '@monorepo-template/jest-transformer',
'workers-shared': '@monorepo-template/jest-transformer',

'\\.[jt]s$': [
'ts-jest',
Expand Down
17 changes: 9 additions & 8 deletions packages/authn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@
"production:deploy": "wrangler deploy src/index.ts --env production --outdir dist",
"production:deploy:dry-run": "wrangler deploy src/index.ts --dry-run --env production --outdir dist",
"production:init": "yarn run production:init:emails && yarn run production:init:oauth && yarn run production:init:users",
"production:init:emails": "wrangler d1 execute authn --file=./sql/emails.sql",
"production:init:oauth": "wrangler d1 execute authn --file=./sql/oauth.sql",
"production:init:users": "wrangler d1 execute authn --file=./sql/users.sql",
"staging:deploy": "wrangler deploy src/index.ts --env staging --outdir dist",
"staging:deploy:dry-run": "wrangler deploy src/index.ts --dry-run --env staging --outdir dist",
"production:init:emails": "wrangler d1 execute authn --env production --file=./sql/emails.sql --remote",
"production:init:oauth": "wrangler d1 execute authn --env production --file=./sql/oauth.sql --remote",
"production:init:users": "wrangler d1 execute authn --env production --file=./sql/users.sql --remote",
"staging:deploy": "wrangler deploy src/index.ts --env staging --outdir dist --remote",
"staging:deploy:dry-run": "wrangler deploy src/index.ts --dry-run --env staging --outdir dist --remote",
"staging:init": "yarn run staging:init:emails && yarn run staging:init:oauth && yarn run staging:init:users",
"staging:init:emails": "wrangler d1 execute authn-staging --file=./sql/emails.sql",
"staging:init:oauth": "wrangler d1 execute authn-staging --file=./sql/oauth.sql",
"staging:init:users": "wrangler d1 execute authn-staging --file=./sql/users.sql",
"staging:init:emails": "wrangler d1 execute authn-staging --env staging --file=./sql/emails.sql --remote",
"staging:init:oauth": "wrangler d1 execute authn-staging --env staging --file=./sql/oauth.sql --remote",
"staging:init:users": "wrangler d1 execute authn-staging --env staging --file=./sql/users.sql --remote",
"whoami": "wrangler whoami"
},
"dependencies": {
"@quisido/authn-shared": "workspace:^",
"@quisido/workers-shared": "workspace:^",
"cookie": "^0.6.0",
"fmrs": "workspace:^",
"form-urlencoded": "^6.1.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/authn/sql/emails.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS [emails] (
"address" VARCHAR(255) NOT NULL PRIMARY KEY,
"address" TEXT NOT NULL PRIMARY KEY,
"userId" UNSIGNED INTEGER NOT NULL
);

Expand Down
4 changes: 2 additions & 2 deletions packages/authn/sql/oauth.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS [oauth] (
"userId" UNSIGNED INTEGER NOT NULL,
"oAuthProvider" UNSIGNED SMALLINT NOT NULL,
"oAuthId" VARCHAR(255) NOT NULL
"oAuthProvider" UNSIGNED INTEGER NOT NULL,
"oAuthId" TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_oauth_oAuthProvider_oAuthId ON oauth(oAuthProvider, oAuthId);
Expand Down
8 changes: 4 additions & 4 deletions packages/authn/sql/users.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS [users] (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"firstName" VARCHAR(255) DEFAULT NULL,
"fullName" VARCHAR(255) DEFAULT NULL,
"gender" UNSIGNED TINYINT NOT NULL DEFAULT 0,
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"firstName" TEXT DEFAULT NULL,
"fullName" TEXT DEFAULT NULL,
"gender" UNSIGNED INTEGER NOT NULL DEFAULT 0,
"registrationTimestamp" UNSIGNED INTEGER NOT NULL
);
1 change: 1 addition & 0 deletions packages/authn/src/constants/metric-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ enum MetricName {
// quisi.do
AuthnIdCreated = 'authn-id.created',
AuthnIdError = 'authn-id.error',
InvalidUsageDataset = 'dataset.usage.invalid',
}

export default MetricName;
4 changes: 1 addition & 3 deletions packages/authn/src/features/create-return-href.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export default function createReturnHref(): string {

const stateSearchParam: string | null = getRequestSearchParam('state');
if (stateSearchParam === null) {
throw mapCauseToError({
code: ErrorCode.MissingState,
});
throw mapCauseToError({ code: ErrorCode.MissingState });
}

const getStateJson = (): unknown => {
Expand Down
7 changes: 7 additions & 0 deletions packages/authn/src/features/get-database-user-id.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ErrorCode } from '@quisido/authn-shared';
import { AccountNumber, UsageType } from '@quisido/workers-shared';
import MetricName from '../constants/metric-name.js';
import type OAuthProvider from '../constants/oauth-provider.js';
import getTelemetry from '../utils/get-telemetry.js';
import isObject from '../utils/is-object.js';
import mapCauseToError from '../utils/map-cause-to-error.js';
import getDatabase from './get-database.js';
import getUsage from './get-usage.js';

const SELECT_USERID_FROM_OAUTH_QUERY = `
SELECT \`userId\`
Expand All @@ -20,11 +22,16 @@ export default async function getDatabaseUserId(
): Promise<number | null> {
const db: D1Database = getDatabase();
const { emitPrivateMetric, emitPublicMetric } = getTelemetry();
const use = getUsage();

const statement: D1PreparedStatement = db
.prepare(SELECT_USERID_FROM_OAUTH_QUERY)
.bind(oAuthProvider, oAuthId);

use({
account: AccountNumber.Quisido,
type: UsageType.D1Read,
});
const {
meta: { duration, rows_read: rowsRead, size_after: sizeAfter },
results,
Expand Down
43 changes: 43 additions & 0 deletions packages/authn/src/features/get-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ErrorCode } from '@quisido/authn-shared';
import { Product, type UsageType } from '@quisido/workers-shared';
import getEnv from '../utils/get-env.js';
import isAnaylticsEngineDataset from '../utils/is-analytics-engine-dataset.js';
import mapCauseToError from '../utils/map-cause-to-error.js';

interface Options {
readonly account: number;
readonly count?: number | undefined;
readonly per?: number | undefined;
readonly type: UsageType;
}

const ONCE = 1;
const QUISIDO_AUTHENTICATION_PROJECT = 0;
const SINGLE = 1;

export default function getUsage(): (options: Options) => void {
const { USAGE } = getEnv();
if (!isAnaylticsEngineDataset(USAGE)) {
throw mapCauseToError({
code: ErrorCode.InvalidUsageDataset,
privateData: JSON.stringify(USAGE),
publicData: typeof USAGE,
});
}

return function use({
account,
count = ONCE,
per = SINGLE,
type,
}: Options): void {
USAGE.writeDataPoint({
doubles: [type, count, per],
indexes: [
account.toString(),
Product.Authentication.toString(),
QUISIDO_AUTHENTICATION_PROJECT.toString(),
],
});
}
}
3 changes: 2 additions & 1 deletion packages/authn/src/features/handle-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import State from './state.js';

export default async function handleFetch(
fetch: Fetcher['fetch'],
console: Console,
request: Request,
env: unknown,
ctx: ExecutionContext,
): Promise<Response> {
const traceId: string = createTraceId();
const state: State = new State(fetch, request, ctx, traceId);
const state: State = new State(fetch, console, request, ctx, traceId);
return stateVar.run(state, async (): Promise<Response> => {
if (typeof env === 'undefined') {
return handleMissingIsolateEnvironment();
Expand Down
9 changes: 9 additions & 0 deletions packages/authn/src/features/map-user-id-to-response.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AccountNumber, UsageType } from '@quisido/workers-shared';
import StatusCode from '../constants/status-code.js';
import { SECONDS_PER_DAY } from '../constants/time.js';
import createAuthnId from '../utils/create-authn-id.js';
import getNowSeconds from '../utils/get-now-seconds.js';
import getTelemetry from '../utils/get-telemetry.js';
import getAuthnUserIdsNamespace from './get-authn-user-ids-namespace.js';
import getUsage from './get-usage.js';
import handlePutAuthnUserIdError from './handle-put-authn-user-id-error.js';
import handlePutAuthnUserId from './handle-put-authn-user-id.js';
import mapAuthnIdToResponseHeaders from './map-authn-id-to-response-headers.js';
Expand All @@ -13,6 +15,7 @@ export default function mapUserIdToResponse(id: number): Response {
const authnUserIds: KVNamespace = getAuthnUserIdsNamespace();
const nowSeconds: number = getNowSeconds();
const { affect } = getTelemetry();
const use = getUsage();

/**
* We set the ID asynchronously because there are good odds that it
Expand All @@ -23,6 +26,12 @@ export default function mapUserIdToResponse(id: number): Response {
*/
const startTime: number = Date.now();
const expiration: number = nowSeconds + SECONDS_PER_DAY;
use({
account: AccountNumber.Quisido,
count: `${authnId}=${id.toString()}`.length,
per: SECONDS_PER_DAY,
type: UsageType.KVStore,
});
affect(
authnUserIds
.put(authnId, id.toString(), {
Expand Down
41 changes: 41 additions & 0 deletions packages/authn/src/features/put-database-user-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference types="@cloudflare/workers-types" />
import { AccountNumber, UsageType } from "@quisido/workers-shared";
import getTelemetry from "../utils/get-telemetry.js";
import getDatabase from "./get-database.js";
import getUsage from "./get-usage.js";
import handleInsertIntoEmailsError from './handle-insert-into-emails-error.js';
import handleInsertIntoEmailsResponse from './handle-insert-into-emails-response.js';

interface Options {
readonly email: string;
readonly userId: number;
}

const INSERT_INTO_EMAILS_QUERY = `
INSERT INTO \`emails\` (\`address\`, \`userId\`)
VALUES (?, ?);
`;

export default function putDatabaseUserEmail({
email,
userId,
}: Options): void {
const db: D1Database = getDatabase();
const { affect } = getTelemetry();
const use = getUsage();

use({
account: AccountNumber.Quisido,
type: UsageType.D1Write,
});
const insertIntoEmails: Promise<D1Response> = db
.prepare(INSERT_INTO_EMAILS_QUERY)
.bind(email, userId)
.run();

affect(
insertIntoEmails
.then(handleInsertIntoEmailsResponse(userId))
.catch(handleInsertIntoEmailsError(userId)),
);
}
26 changes: 9 additions & 17 deletions packages/authn/src/features/put-database-user-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { AccountNumber, UsageType } from "@quisido/workers-shared";
import MetricName from "../constants/metric-name.js";
import type OAuthProvider from "../constants/oauth-provider.js";
import getTelemetry from "../utils/get-telemetry.js";
import getDatabase from "./get-database.js";
import handleInsertIntoEmailsError from './handle-insert-into-emails-error.js';
import handleInsertIntoEmailsResponse from './handle-insert-into-emails-response.js';
import getUsage from "./get-usage.js";
import handleInsertIntoOAuthError from './handle-insert-into-oauth-error.js';
import handleInsertIntoOAuthResponse from './handle-insert-into-oauth-response.js';
import putDatabaseUserEmail from "./put-database-user-email.js";

interface Options {
readonly changes: number;
Expand All @@ -17,11 +18,6 @@ interface Options {
readonly userId: number;
}

const INSERT_INTO_EMAILS_QUERY = `
INSERT INTO \`emails\` (\`address\`, \`userId\`)
VALUES (?, ?);
`;

const INSERT_INTO_OAUTH_QUERY = `
INSERT INTO \`oauth\` (\`userId\`, \`oAuthProvider\`, \`oAuthId\`)
VALUES (?, ?, ?);
Expand All @@ -38,6 +34,7 @@ export default function putDatabaseUserMetadata({
}: Options): number {
const db: D1Database = getDatabase();
const { affect, emitPublicMetric } = getTelemetry();
const use = getUsage();
emitPublicMetric({
changes,
duration,
Expand All @@ -47,6 +44,10 @@ export default function putDatabaseUserMetadata({
});

// Associate user ID with OAuth ID.
use({
account: AccountNumber.Quisido,
type: UsageType.D1Write,
});
const insertIntoOAuth: Promise<D1Response> = db
.prepare(INSERT_INTO_OAUTH_QUERY)
.bind(userId, oAuthProvider, oAuthId)
Expand All @@ -60,16 +61,7 @@ export default function putDatabaseUserMetadata({

// Associate user ID with email.
if (email !== null) {
const insertIntoEmails: Promise<D1Response> = db
.prepare(INSERT_INTO_EMAILS_QUERY)
.bind(email, userId)
.run();

affect(
insertIntoEmails
.then(handleInsertIntoEmailsResponse(userId))
.catch(handleInsertIntoEmailsError(userId)),
);
putDatabaseUserEmail({ email, userId });
}

return userId;
Expand Down

0 comments on commit e334dee

Please sign in to comment.