@willyim/idp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @willyim/idp — login for apps that don't own identity.
3
+ *
4
+ * The IdP is the single source of truth for who someone is and what they may
5
+ * do. The app runs no auth framework: it owns one session table, which is a
6
+ * handle to IdP truth rather than a record of it.
7
+ *
8
+ * Zero runtime dependencies. `fetch` + WebCrypto + `Request`/`Response`, so the
9
+ * same build runs on Cloudflare Workers, Node ≥20 and Bun.
10
+ *
11
+ * @willyim/idp this module — client + session
12
+ * @willyim/idp/drizzle the session store, and the `idp_session` table
13
+ * @willyim/idp/react-router the auth route and the loader guards
14
+ */
15
+ export { createIdpClient, createPkce, DEFAULT_SCOPES, IdpError, type AuthorizationUrlInput, type Discovery, type IdpClient, type IdpClientOptions, type Tokens, } from "./client.js";
16
+ export { createIdp, DEFAULT_SESSION_COOKIE, safeNext, type Idp, type IdpOptions, type Session, type SessionOptions, } from "./session.js";
17
+ export { memorySessions, type MemorySessionStore, type SessionRecord, type SessionStore, } from "./store.js";
18
+ export { grants, normalizeClaims, PERMISSIONS_CLAIM, WORKSPACES_CLAIM, type Actor, type Claims, type Workspace, } from "./claims.js";
19
+ export { parseDuration, type Duration } from "./duration.js";
20
+ export { clearCookie, parseCookies, readCookie, serializeCookie, type CookieOptions, } from "./cookie.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,QAAQ,EACR,KAAK,GAAG,EACR,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,cAAc,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,SAAS,GACf,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC5D,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @willyim/idp — login for apps that don't own identity.
3
+ *
4
+ * The IdP is the single source of truth for who someone is and what they may
5
+ * do. The app runs no auth framework: it owns one session table, which is a
6
+ * handle to IdP truth rather than a record of it.
7
+ *
8
+ * Zero runtime dependencies. `fetch` + WebCrypto + `Request`/`Response`, so the
9
+ * same build runs on Cloudflare Workers, Node ≥20 and Bun.
10
+ *
11
+ * @willyim/idp this module — client + session
12
+ * @willyim/idp/drizzle the session store, and the `idp_session` table
13
+ * @willyim/idp/react-router the auth route and the loader guards
14
+ */
15
+ export { createIdpClient, createPkce, DEFAULT_SCOPES, IdpError, } from "./client.js";
16
+ export { createIdp, DEFAULT_SESSION_COOKIE, safeNext, } from "./session.js";
17
+ export { memorySessions, } from "./store.js";
18
+ export { grants, normalizeClaims, PERMISSIONS_CLAIM, WORKSPACES_CLAIM, } from "./claims.js";
19
+ export { parseDuration } from "./duration.js";
20
+ export { clearCookie, parseCookies, readCookie, serializeCookie, } from "./cookie.js";
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @willyim/idp/react-router — the whole auth layer as one splat route and two
3
+ * guards.
4
+ *
5
+ * // app/routes/auth.$.tsx
6
+ * export const { loader, action } = createAuthRoute(({ context }) => context.idp)
7
+ *
8
+ * // anywhere
9
+ * const session = await requireSession(args)
10
+ * const session = await requirePermission(args, "admin")
11
+ *
12
+ * No react-router import: the adapter only ever produces `Response`s and
13
+ * functions, both of which react-router already understands. That keeps the
14
+ * subpath dependency-free too, and makes it usable from any framework whose
15
+ * handlers take a `Request`.
16
+ */
17
+ import type { Idp, Session } from "../session.js";
18
+ /** The loader/action argument shape, narrowed to what the adapter reads. */
19
+ export type RouteArgs = {
20
+ request: Request;
21
+ context?: unknown;
22
+ params?: Record<string, string | undefined>;
23
+ };
24
+ export type IdpResolver = (args: RouteArgs) => Idp | Promise<Idp>;
25
+ export type AuthRouteOptions = {
26
+ /** Where this splat route is mounted. Default `/auth`. */
27
+ basePath?: string;
28
+ /** Where to land after a login with no `?next=`. Default `/`. */
29
+ defaultRedirect?: string;
30
+ /** Where to land after logout. Default `defaultRedirect`. */
31
+ logoutRedirect?: string;
32
+ /**
33
+ * Also end the IdP's SSO session on logout (RP-initiated logout). Default
34
+ * true. Set false to keep logout app-local — useful while the OAuth client
35
+ * has no `enable_end_session` / `post_logout_redirect_uris` registered.
36
+ */
37
+ idpLogout?: boolean;
38
+ /**
39
+ * The registered OAuth redirect URI. Defaults to `${origin}${basePath}/callback`,
40
+ * which is what you register in the IdP console.
41
+ */
42
+ redirectUri?: (request: Request) => string;
43
+ };
44
+ /**
45
+ * Serves `/auth/login`, `/auth/callback` and `/auth/logout` from a single splat
46
+ * route. `loader` covers the GET navigations; `action` covers a POSTed logout
47
+ * (and login), which is what you want from a form so a link prefetch can't log
48
+ * anyone out.
49
+ */
50
+ export declare function createAuthRoute(getIdp: IdpResolver, options?: AuthRouteOptions): {
51
+ loader: (args: RouteArgs) => Promise<Response>;
52
+ action: (args: RouteArgs) => Promise<Response>;
53
+ };
54
+ export type GuardOptions = {
55
+ /** Use this Idp instead of `context.idp`. */
56
+ idp?: Idp;
57
+ /** Where `requireSession` sends anonymous visitors. Default `/auth/login`. */
58
+ loginPath?: string;
59
+ /** Skip the `?next=` round trip. */
60
+ preserveNext?: boolean;
61
+ };
62
+ /** The session for this request, or null. Never throws for the anonymous case. */
63
+ export declare function getSession(args: RouteArgs, options?: GuardOptions): Promise<Session | null>;
64
+ /**
65
+ * The session, or a redirect to login preserving where the visitor was going.
66
+ * Throwing a `Response` is how react-router short-circuits a loader.
67
+ */
68
+ export declare function requireSession(args: RouteArgs, options?: GuardOptions): Promise<Session>;
69
+ /**
70
+ * `requireSession` plus a permission check. A logged-out visitor is redirected
71
+ * to log in; a logged-in one without the permission gets a 403 — re-authenticating
72
+ * wouldn't help them.
73
+ */
74
+ export declare function requirePermission(args: RouteArgs, permission: string, options?: GuardOptions): Promise<Session>;
75
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react-router/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGjD,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;CAC3C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,gBAAqB;mBA+ChE,SAAS;mBACT,SAAS;EAE3B;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oCAAoC;IACpC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,kFAAkF;AAClF,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAEzB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @willyim/idp/react-router — the whole auth layer as one splat route and two
3
+ * guards.
4
+ *
5
+ * // app/routes/auth.$.tsx
6
+ * export const { loader, action } = createAuthRoute(({ context }) => context.idp)
7
+ *
8
+ * // anywhere
9
+ * const session = await requireSession(args)
10
+ * const session = await requirePermission(args, "admin")
11
+ *
12
+ * No react-router import: the adapter only ever produces `Response`s and
13
+ * functions, both of which react-router already understands. That keeps the
14
+ * subpath dependency-free too, and makes it usable from any framework whose
15
+ * handlers take a `Request`.
16
+ */
17
+ import { safeNext } from "../session.js";
18
+ /**
19
+ * Serves `/auth/login`, `/auth/callback` and `/auth/logout` from a single splat
20
+ * route. `loader` covers the GET navigations; `action` covers a POSTed logout
21
+ * (and login), which is what you want from a form so a link prefetch can't log
22
+ * anyone out.
23
+ */
24
+ export function createAuthRoute(getIdp, options = {}) {
25
+ const basePath = (options.basePath ?? "/auth").replace(/\/+$/, "");
26
+ const defaultRedirect = options.defaultRedirect ?? "/";
27
+ const logoutRedirect = options.logoutRedirect ?? defaultRedirect;
28
+ const redirectUriFor = (request) => options.redirectUri?.(request) ?? new URL(`${basePath}/callback`, request.url).toString();
29
+ async function handle(args) {
30
+ const url = new URL(args.request.url);
31
+ const action = url.pathname.slice(url.pathname.lastIndexOf("/") + 1);
32
+ const idp = await getIdp(args);
33
+ if (action === "login") {
34
+ const next = safeNext(url.searchParams.get("next"));
35
+ const { url: authorizeUrl, headers } = await idp.startLogin({
36
+ redirectUri: redirectUriFor(args.request),
37
+ next,
38
+ prompt: url.searchParams.get("prompt") ?? undefined,
39
+ loginHint: url.searchParams.get("login_hint") ?? undefined,
40
+ });
41
+ return redirect(authorizeUrl, headers);
42
+ }
43
+ if (action === "callback") {
44
+ const { headers, next } = await idp.completeLogin(args.request, {
45
+ redirectUri: redirectUriFor(args.request),
46
+ });
47
+ return redirect(next === "/" ? defaultRedirect : next, headers);
48
+ }
49
+ if (action === "logout") {
50
+ const redirectTo = new URL(logoutRedirect, url.origin).toString();
51
+ const { headers, url: endSession } = await idp.logout(args.request, {
52
+ redirectTo,
53
+ idpLogout: options.idpLogout,
54
+ });
55
+ // The local session is already gone. If there's no usable end-session URL
56
+ // we just land the visitor back on the app; the IdP's SSO session simply
57
+ // outlives the app's.
58
+ return redirect(endSession ?? redirectTo, headers);
59
+ }
60
+ return new Response("not found", { status: 404 });
61
+ }
62
+ return {
63
+ loader: (args) => handle(args),
64
+ action: (args) => handle(args),
65
+ };
66
+ }
67
+ /** The session for this request, or null. Never throws for the anonymous case. */
68
+ export async function getSession(args, options = {}) {
69
+ return resolveIdp(args, options).getSession(args.request);
70
+ }
71
+ /**
72
+ * The session, or a redirect to login preserving where the visitor was going.
73
+ * Throwing a `Response` is how react-router short-circuits a loader.
74
+ */
75
+ export async function requireSession(args, options = {}) {
76
+ const session = await getSession(args, options);
77
+ if (session)
78
+ return session;
79
+ const url = new URL(args.request.url);
80
+ const loginUrl = new URL(options.loginPath ?? "/auth/login", url.origin);
81
+ if (options.preserveNext !== false) {
82
+ loginUrl.searchParams.set("next", `${url.pathname}${url.search}`);
83
+ }
84
+ throw redirect(loginUrl.toString());
85
+ }
86
+ /**
87
+ * `requireSession` plus a permission check. A logged-out visitor is redirected
88
+ * to log in; a logged-in one without the permission gets a 403 — re-authenticating
89
+ * wouldn't help them.
90
+ */
91
+ export async function requirePermission(args, permission, options = {}) {
92
+ const session = await requireSession(args, options);
93
+ if (!session.can(permission)) {
94
+ throw new Response(`Forbidden: missing ${permission}`, { status: 403 });
95
+ }
96
+ return session;
97
+ }
98
+ function resolveIdp(args, options) {
99
+ const idp = options.idp ?? args.context?.idp;
100
+ if (!idp) {
101
+ throw new Error("@willyim/idp/react-router: no Idp found. Put it on the load context as `idp`, " +
102
+ "or pass `{ idp }` to the guard.");
103
+ }
104
+ return idp;
105
+ }
106
+ function redirect(location, headers) {
107
+ const merged = new Headers(headers);
108
+ merged.set("location", location);
109
+ return new Response(null, { status: 302, headers: merged });
110
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Layer 1 — the app session. The cookie holds an HMAC-signed opaque session id;
3
+ * everything else lives in the row. The signature is checked before the store is
4
+ * touched, so a junk cookie never costs a query.
5
+ *
6
+ * Permissions are a TTL-cached projection of IdP truth: inside the freshness
7
+ * window we read the row, past it we re-read `/userinfo`. That per-freshness
8
+ * call doubles as a liveness ping — it is how a revocation at the IdP reaches
9
+ * the app, in minutes rather than in session-lengths.
10
+ */
11
+ import type { Actor, Claims, Workspace } from "./claims.js";
12
+ import { type IdpClientOptions } from "./client.js";
13
+ import { type Duration } from "./duration.js";
14
+ import type { SessionStore } from "./store.js";
15
+ export declare const DEFAULT_SESSION_COOKIE = "idp_session";
16
+ export type SessionOptions = {
17
+ /** HMAC key for the session cookie. Rotating it logs everyone out. */
18
+ secret: string;
19
+ /** Idle timeout. Slides on use via `updateAge`. Default `"7d"`. */
20
+ expiresIn?: Duration;
21
+ /** Only slide the expiry once the session is this far into its life. Default `"1d"`. */
22
+ updateAge?: Duration;
23
+ /** How long cached claims are trusted before `/userinfo` is re-read. Default `"5m"`. */
24
+ freshness?: Duration;
25
+ /** Hard cap measured from login — no amount of sliding gets past it. */
26
+ absoluteExpiresIn?: Duration;
27
+ cookieName?: string;
28
+ cookieDomain?: string;
29
+ /** Off only for local http development. Default true. */
30
+ secure?: boolean;
31
+ sameSite?: "lax" | "strict" | "none";
32
+ path?: string;
33
+ };
34
+ export type IdpOptions = IdpClientOptions & {
35
+ sessions: SessionStore;
36
+ session: SessionOptions;
37
+ /** Testing hook: the clock the session logic reads. */
38
+ now?: () => Date;
39
+ /** Emit dev-mode warnings (clamped session length, …). Default: off in production. */
40
+ debug?: boolean;
41
+ };
42
+ /**
43
+ * A live session. Safe to project into loader data — it carries claims, never
44
+ * tokens. `can()` and `renewCookie()` are methods, so a plain serialization of
45
+ * this object gives the browser exactly the data and none of the behaviour.
46
+ */
47
+ export type Session = {
48
+ id: string;
49
+ sub: string;
50
+ email: string;
51
+ name: string | null;
52
+ image: string | null;
53
+ permissions: string[];
54
+ workspaces: Workspace[];
55
+ actor: Actor | null;
56
+ createdAt: Date;
57
+ expiresAt: Date;
58
+ /** Exact match, or a `resource:*` / `*` grant that covers it. */
59
+ can(permission: string): boolean;
60
+ /**
61
+ * The `Set-Cookie` that extends the browser's cookie to match a session whose
62
+ * expiry just slid, or null when nothing changed. Attach it to the response
63
+ * if you want sliding sessions to survive the cookie's own `Max-Age`.
64
+ */
65
+ renewCookie(): string | null;
66
+ };
67
+ export type Idp = ReturnType<typeof createIdp>;
68
+ export declare function createIdp(options: IdpOptions): {
69
+ /** The Layer 0 client, for anything the session layer doesn't wrap. */
70
+ client: {
71
+ discover: () => Promise<import("./client.js").Discovery>;
72
+ authorizationUrl(input: import("./client.js").AuthorizationUrlInput): Promise<string>;
73
+ exchangeCode(input: {
74
+ code: string;
75
+ redirectUri: string;
76
+ codeVerifier: string;
77
+ }): Promise<import("./client.js").Tokens>;
78
+ refresh(refreshToken: string): Promise<import("./client.js").Tokens>;
79
+ userinfo(accessToken: string): Promise<Claims>;
80
+ logoutUrl(input: {
81
+ idToken?: string | null;
82
+ redirectTo?: string;
83
+ }): Promise<string | null>;
84
+ };
85
+ /**
86
+ * Step one of login: where to send the browser, plus the `Set-Cookie` that
87
+ * carries the CSRF state and the PKCE verifier through the round trip.
88
+ */
89
+ startLogin(input: {
90
+ redirectUri: string;
91
+ /** Path to return to after the callback. Only same-site paths are honoured. */
92
+ next?: string;
93
+ prompt?: string;
94
+ loginHint?: string;
95
+ }): Promise<{
96
+ url: string;
97
+ headers: Headers;
98
+ }>;
99
+ /**
100
+ * Step two: verify the state, exchange the code, read live claims, and write
101
+ * the session row. Returns the `Set-Cookie` headers and where to go next.
102
+ */
103
+ completeLogin(request: Request, input: {
104
+ redirectUri: string;
105
+ }): Promise<{
106
+ session: Session;
107
+ headers: Headers;
108
+ next: string;
109
+ }>;
110
+ /**
111
+ * The session for this request, or null. One store read on the common path;
112
+ * a `/userinfo` round trip once the freshness window has closed.
113
+ */
114
+ getSession(request: Request): Promise<Session | null>;
115
+ /**
116
+ * Log this browser out: delete the row, expire the cookie. The returned
117
+ * headers also clear any half-finished login state.
118
+ */
119
+ destroySession: (request: Request) => Promise<Headers>;
120
+ /** Log a subject out of every browser. Instant — revocation is a row delete. */
121
+ destroyAllSessions(sub: string): Promise<void>;
122
+ /**
123
+ * Log out locally *and* at the IdP. The row is deleted and the cookie
124
+ * expired before anything else happens, so however the RP-initiated logout
125
+ * goes — endpoint missing, client not registered for it, IdP down — the
126
+ * visitor is never left signed in here.
127
+ *
128
+ * `url` is the IdP's end-session URL to send the browser to, or null when
129
+ * there isn't a usable one; redirect somewhere local in that case.
130
+ */
131
+ logout(request: Request, input?: {
132
+ redirectTo?: string;
133
+ idpLogout?: boolean;
134
+ }): Promise<{
135
+ headers: Headers;
136
+ url: string | null;
137
+ }>;
138
+ };
139
+ /** Only same-site paths survive as `?next=` — an open redirect is not a feature. */
140
+ export declare function safeNext(next: string | null | undefined): string | undefined;
141
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"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"}