Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/src/component/_generated/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/src/component/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
REFRESH_GRACE_MS,
SPENT_TOKEN_HORIZON_MS,
MAX_SESSIONS_PER_USER,
} from "./public.ts";
} from "./sessions.ts";

/**
* The component's modules, and the test-only `testApp` module. The fixture file
Expand Down
61 changes: 61 additions & 0 deletions packages/core/src/component/expiry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Token lifetimes: the validated config of a call, and when a refresh token
// expires under it.

import type { Doc } from "./_generated/dataModel.ts";
import {
DEFAULT_ACCESS_TOKEN_TTL_SECONDS,
DEFAULT_REFRESH_TOKEN_TTL_SECONDS,
} from "../lib/ttl.ts";

// `ctx.auth.signJwt` refuses to mint a token that lives longer than an hour,
// so an access-token TTL past that is a misconfiguration.
const MAX_ACCESS_TOKEN_TTL_SECONDS = 60 * 60; // 1 hour

/**
* The token lifetimes for a call, with the app's overrides applied over the
* defaults. Validated so a misconfiguration fails loudly rather than minting
* nonsensical sessions.
*/
export type TtlConfig = {
accessTokenTtlSeconds: number;
refreshTokenTtlSeconds: number;
};

export function resolveTtlConfig(args: {
accessTokenTtlSeconds?: number;
refreshTokenTtlSeconds?: number;
}): TtlConfig {
const accessTokenTtlSeconds =
args.accessTokenTtlSeconds ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
const refreshTokenTtlSeconds =
args.refreshTokenTtlSeconds ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS;
if (accessTokenTtlSeconds <= 0 || refreshTokenTtlSeconds <= 0) {
throw new Error("Token TTLs must be positive.");
}
if (accessTokenTtlSeconds > MAX_ACCESS_TOKEN_TTL_SECONDS) {
throw new Error(
"Access-token TTL must be at most one hour (the deployment refuses " +
"to sign a longer-lived JWT).",
);
}
if (accessTokenTtlSeconds >= refreshTokenTtlSeconds) {
throw new Error(
"Access-token TTL must be shorter than the refresh-token TTL.",
);
}
return { accessTokenTtlSeconds, refreshTokenTtlSeconds };
}

/**
* When a refresh token expires under the given refresh-token TTL.
*
* Expiry is derived rather than stored, so a changed TTL applies to every
* outstanding token. It also means a session's newest token is always its
* last to expire.
*/
export function refreshTokenExpiresAt(
token: Pick<Doc<"refreshTokens">, "_creationTime">,
refreshTokenTtlSeconds: number,
): number {
return token._creationTime + refreshTokenTtlSeconds * 1000;
}
Loading
Loading