@shware/security 3.4.1 → 4.0.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.
- package/README.md +40 -6
- package/dist/auth/attributes.cjs +31 -0
- package/dist/auth/attributes.cjs.map +1 -0
- package/dist/auth/attributes.d.cts +17 -0
- package/dist/auth/attributes.d.cts.map +1 -0
- package/dist/auth/attributes.d.mts +17 -0
- package/dist/auth/attributes.d.mts.map +1 -0
- package/dist/auth/attributes.mjs +27 -0
- package/dist/auth/attributes.mjs.map +1 -0
- package/dist/auth/index.cjs +72 -19
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +2 -2
- package/dist/auth/index.d.cts.map +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +72 -19
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/types.d.cts +32 -2
- package/dist/auth/types.d.cts.map +1 -1
- package/dist/auth/types.d.mts +32 -2
- package/dist/auth/types.d.mts.map +1 -1
- package/dist/email/schema.cjs +3 -1
- package/dist/email/schema.cjs.map +1 -1
- package/dist/email/schema.d.cts +1 -0
- package/dist/email/schema.d.cts.map +1 -1
- package/dist/email/schema.d.mts +1 -0
- package/dist/email/schema.d.mts.map +1 -1
- package/dist/email/schema.mjs +3 -1
- package/dist/email/schema.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/oauth2/types.cjs.map +1 -1
- package/dist/oauth2/types.d.cts +6 -0
- package/dist/oauth2/types.d.cts.map +1 -1
- package/dist/oauth2/types.d.mts +6 -0
- package/dist/oauth2/types.d.mts.map +1 -1
- package/dist/oauth2/types.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -91,10 +91,11 @@ the key itself is used (`github`). The key is the `:registrationId` in the route
|
|
|
91
91
|
### The `onAuthorized` handler
|
|
92
92
|
|
|
93
93
|
Every login method takes an `AuthorizedHandler`: create or update your user and return the
|
|
94
|
-
`Principal` whose `name` is stored in the session.
|
|
94
|
+
`Principal` whose `name` is stored in the session. It gets the request and an `Authorization`
|
|
95
|
+
object — `{ registrationId, userInfo, token?, attributes }` — so fields are named, not positional.
|
|
95
96
|
|
|
96
97
|
```ts
|
|
97
|
-
import type {
|
|
98
|
+
import type { Authorization, Principal, Provider } from '@shware/security';
|
|
98
99
|
|
|
99
100
|
const PROVIDER_BY_REGISTRATION: Record<string, Provider | undefined> = {
|
|
100
101
|
github: 'GITHUB',
|
|
@@ -105,9 +106,7 @@ const PROVIDER_BY_REGISTRATION: Record<string, Provider | undefined> = {
|
|
|
105
106
|
|
|
106
107
|
export async function onAuthorized(
|
|
107
108
|
request: Request,
|
|
108
|
-
registrationId:
|
|
109
|
-
{ claims }: UserInfo,
|
|
110
|
-
token?: OAuth2Token
|
|
109
|
+
{ registrationId, userInfo: { claims } }: Authorization
|
|
111
110
|
): Promise<Principal> {
|
|
112
111
|
const provider = PROVIDER_BY_REGISTRATION[registrationId];
|
|
113
112
|
if (!provider) throw new Error(`Unknown registration: ${registrationId}`);
|
|
@@ -294,6 +293,41 @@ The redirect URI defaults to `${baseUri}/login/oauth2/code/<registrationId>`; se
|
|
|
294
293
|
so register both). `loginOAuth2Code` checks state, exchanges the code, calls `onAuthorized`
|
|
295
294
|
and redirects to `successUri`, or to `errorUri` with `error` and `error_description`.
|
|
296
295
|
|
|
296
|
+
#### Carrying data to `onAuthorized` (`attributes`)
|
|
297
|
+
|
|
298
|
+
Something known when the flow starts may be needed when the user comes back — a referral code in
|
|
299
|
+
a cookie, a `?ref=` parameter, where to go after sign-in. The callback is the wrong place to look
|
|
300
|
+
for it: Apple's `form_post` is a cross-site POST that drops `SameSite=Lax` cookies, and query
|
|
301
|
+
parameters do not survive the provider round trip. Attach it to the authorization request instead
|
|
302
|
+
(like Spring Security's `OAuth2AuthorizationRequest` attributes):
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
app.get(PATH.OAUTH2_AUTHORIZATION, (c) =>
|
|
306
|
+
auth.oauth2Authorization(c.req.raw, {
|
|
307
|
+
attributes: { referralCode: getCookie(c, 'REFERRAL_CODE') },
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
export async function onAuthorized(request: Request, { attributes }: Authorization) {
|
|
312
|
+
const referralCode = attributes.referralCode;
|
|
313
|
+
// ...
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- Stored with the authorization request in the session (the same JSON as `state` and the PKCE
|
|
318
|
+
verifier), never sent to the provider, so no extra storage, expiry or cleanup.
|
|
319
|
+
- Handed to `onAuthorized` as `authorization.attributes` only after the session, `state` and
|
|
320
|
+
registration check out, and removed with the authorization request after a successful login,
|
|
321
|
+
so they are used once.
|
|
322
|
+
- String values only; `undefined` values are dropped; at most 2 KB serialized (they are written
|
|
323
|
+
with the session).
|
|
324
|
+
- Logins that post a body — native, One Tap (JSON) and email — take an optional `attributes`
|
|
325
|
+
field in that body instead, since the SDK consumes the body before `onAuthorized` runs:
|
|
326
|
+
`{ "id_token": "…", "attributes": { "referralCode": "abc123" } }`. Same rules (string values,
|
|
327
|
+
at most 2 KB); an invalid one is a `400`. Without it the handler gets `attributes: {}`.
|
|
328
|
+
- They come from the browser that started the flow: trust them like a cookie — fine for
|
|
329
|
+
attribution or a post-login destination, not for authorization decisions.
|
|
330
|
+
|
|
297
331
|
### Native login
|
|
298
332
|
|
|
299
333
|
Mobile apps sign in with the platform SDK and post the result to
|
|
@@ -345,7 +379,7 @@ app.post(PATH.LOGIN_EMAIL, (c) => auth.loginEmail(c.req.raw, onAuthorized));
|
|
|
345
379
|
|
|
346
380
|
- `POST /auth/sendEmailVerificationCode` with `{ email, turnstileToken }`.
|
|
347
381
|
- `POST /login/email` with `{ email, verificationCode }`. On success `onAuthorized` is called with
|
|
348
|
-
|
|
382
|
+
`registrationId: 'EMAIL'`, claims `{ sub: email, email, email_verified: true }` and no `token`.
|
|
349
383
|
|
|
350
384
|
`sendPhoneVerificationCode` and `loginPhone` exist but currently answer `501 not_implemented`.
|
|
351
385
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let zod_mini = require("zod/mini");
|
|
3
|
+
//#region src/auth/attributes.ts
|
|
4
|
+
/** Upper bound on serialized attributes: they ride in a session or a request body. */
|
|
5
|
+
const MAX_ATTRIBUTES_BYTES = 2048;
|
|
6
|
+
function attributesSize(attributes) {
|
|
7
|
+
return new TextEncoder().encode(JSON.stringify(attributes)).length;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The optional `attributes` field of a login body (native, One Tap, email): string values only,
|
|
11
|
+
* at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the
|
|
12
|
+
* client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.
|
|
13
|
+
*/
|
|
14
|
+
const attributesSchema = (0, zod_mini.optional)((0, zod_mini.record)((0, zod_mini.string)(), (0, zod_mini.string)()).check((0, zod_mini.refine)((value) => attributesSize(value) <= MAX_ATTRIBUTES_BYTES, { message: `attributes must be at most ${MAX_ATTRIBUTES_BYTES} bytes` })));
|
|
15
|
+
/**
|
|
16
|
+
* Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when
|
|
17
|
+
* present but invalid.
|
|
18
|
+
*/
|
|
19
|
+
function bodyAttributes(body) {
|
|
20
|
+
const value = typeof body === "object" && body !== null && "attributes" in body ? body.attributes : void 0;
|
|
21
|
+
const parsed = attributesSchema.safeParse(value);
|
|
22
|
+
if (!parsed.success) return null;
|
|
23
|
+
return parsed.data ?? {};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
exports.MAX_ATTRIBUTES_BYTES = MAX_ATTRIBUTES_BYTES;
|
|
27
|
+
exports.attributesSchema = attributesSchema;
|
|
28
|
+
exports.attributesSize = attributesSize;
|
|
29
|
+
exports.bodyAttributes = bodyAttributes;
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=attributes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.cjs","names":["optional","record","string","refine"],"sources":["../../src/auth/attributes.ts"],"sourcesContent":["import { optional, record, refine, string } from 'zod/mini';\n\n/** Upper bound on serialized attributes: they ride in a session or a request body. */\nexport const MAX_ATTRIBUTES_BYTES = 2048;\n\nexport function attributesSize(attributes: Record<string, string>) {\n return new TextEncoder().encode(JSON.stringify(attributes)).length;\n}\n\n/**\n * The optional `attributes` field of a login body (native, One Tap, email): string values only,\n * at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the\n * client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.\n */\nexport const attributesSchema = optional(\n record(string(), string()).check(\n refine((value) => attributesSize(value) <= MAX_ATTRIBUTES_BYTES, {\n message: `attributes must be at most ${MAX_ATTRIBUTES_BYTES} bytes`,\n })\n )\n);\n\n/**\n * Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when\n * present but invalid.\n */\nexport function bodyAttributes(body: unknown): Record<string, string> | null {\n const value =\n typeof body === 'object' && body !== null && 'attributes' in body ? body.attributes : undefined;\n const parsed = attributesSchema.safeParse(value);\n if (!parsed.success) return null;\n return parsed.data ?? {};\n}\n"],"mappings":";;;;AAGA,MAAa,uBAAuB;AAEpC,SAAgB,eAAe,YAAoC;CACjE,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,UAAU,CAAC,CAAC,CAAC;AAC9D;;;;;;AAOA,MAAa,oBAAA,GAAmBA,SAAAA,SAAAA,EAAAA,GAC9BC,SAAAA,OAAAA,EAAAA,GAAOC,SAAAA,OAAAA,CAAO,IAAA,GAAGA,SAAAA,OAAAA,CAAO,CAAC,CAAC,CAAC,OAAA,GACzBC,SAAAA,OAAAA,EAAQ,UAAU,eAAe,KAAK,KAAK,sBAAsB,EAC/D,SAAS,8BAA8B,qBAAqB,QAC9D,CAAC,CACH,CACF;;;;;AAMA,SAAgB,eAAe,MAA8C;CAC3E,MAAM,QACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,gBAAgB,OAAO,KAAK,aAAa,KAAA;CACxF,MAAM,SAAS,iBAAiB,UAAU,KAAK;CAC/C,IAAI,CAAC,OAAO,SAAS,OAAO;CAC5B,OAAO,OAAO,QAAQ,CAAC;AACzB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/auth/attributes.d.ts
|
|
2
|
+
/** Upper bound on serialized attributes: they ride in a session or a request body. */
|
|
3
|
+
export declare const MAX_ATTRIBUTES_BYTES = 2048;
|
|
4
|
+
export declare function attributesSize(attributes: Record<string, string>): number;
|
|
5
|
+
/**
|
|
6
|
+
* The optional `attributes` field of a login body (native, One Tap, email): string values only,
|
|
7
|
+
* at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the
|
|
8
|
+
* client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.
|
|
9
|
+
*/
|
|
10
|
+
export declare const attributesSchema: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniRecord<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniString<string>>>;
|
|
11
|
+
/**
|
|
12
|
+
* Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when
|
|
13
|
+
* present but invalid.
|
|
14
|
+
*/
|
|
15
|
+
export declare function bodyAttributes(body: unknown): Record<string, string> | null;
|
|
16
|
+
//#endregion
|
|
17
|
+
//# sourceMappingURL=attributes.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.d.cts","names":[],"sources":["../../src/auth/attributes.ts"],"mappings":";;qBAGa;wBAEG,eAAe,YAAY;;;;;;qBAS9B,qCAAgB,mCAAA,iCAAA,0CAAA;;;;;wBAYb,eAAe,gBAAgB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/auth/attributes.d.ts
|
|
2
|
+
/** Upper bound on serialized attributes: they ride in a session or a request body. */
|
|
3
|
+
export declare const MAX_ATTRIBUTES_BYTES = 2048;
|
|
4
|
+
export declare function attributesSize(attributes: Record<string, string>): number;
|
|
5
|
+
/**
|
|
6
|
+
* The optional `attributes` field of a login body (native, One Tap, email): string values only,
|
|
7
|
+
* at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the
|
|
8
|
+
* client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.
|
|
9
|
+
*/
|
|
10
|
+
export declare const attributesSchema: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniRecord<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniString<string>>>;
|
|
11
|
+
/**
|
|
12
|
+
* Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when
|
|
13
|
+
* present but invalid.
|
|
14
|
+
*/
|
|
15
|
+
export declare function bodyAttributes(body: unknown): Record<string, string> | null;
|
|
16
|
+
//#endregion
|
|
17
|
+
//# sourceMappingURL=attributes.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.d.mts","names":[],"sources":["../../src/auth/attributes.ts"],"mappings":";;qBAGa;wBAEG,eAAe,YAAY;;;;;;qBAS9B,qCAAgB,mCAAA,iCAAA,0CAAA;;;;;wBAYb,eAAe,gBAAgB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { optional, record, refine, string } from "zod/mini";
|
|
2
|
+
//#region src/auth/attributes.ts
|
|
3
|
+
/** Upper bound on serialized attributes: they ride in a session or a request body. */
|
|
4
|
+
const MAX_ATTRIBUTES_BYTES = 2048;
|
|
5
|
+
function attributesSize(attributes) {
|
|
6
|
+
return new TextEncoder().encode(JSON.stringify(attributes)).length;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The optional `attributes` field of a login body (native, One Tap, email): string values only,
|
|
10
|
+
* at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the
|
|
11
|
+
* client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.
|
|
12
|
+
*/
|
|
13
|
+
const attributesSchema = optional(record(string(), string()).check(refine((value) => attributesSize(value) <= MAX_ATTRIBUTES_BYTES, { message: `attributes must be at most ${MAX_ATTRIBUTES_BYTES} bytes` })));
|
|
14
|
+
/**
|
|
15
|
+
* Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when
|
|
16
|
+
* present but invalid.
|
|
17
|
+
*/
|
|
18
|
+
function bodyAttributes(body) {
|
|
19
|
+
const value = typeof body === "object" && body !== null && "attributes" in body ? body.attributes : void 0;
|
|
20
|
+
const parsed = attributesSchema.safeParse(value);
|
|
21
|
+
if (!parsed.success) return null;
|
|
22
|
+
return parsed.data ?? {};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { MAX_ATTRIBUTES_BYTES, attributesSchema, attributesSize, bodyAttributes };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=attributes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.mjs","names":[],"sources":["../../src/auth/attributes.ts"],"sourcesContent":["import { optional, record, refine, string } from 'zod/mini';\n\n/** Upper bound on serialized attributes: they ride in a session or a request body. */\nexport const MAX_ATTRIBUTES_BYTES = 2048;\n\nexport function attributesSize(attributes: Record<string, string>) {\n return new TextEncoder().encode(JSON.stringify(attributes)).length;\n}\n\n/**\n * The optional `attributes` field of a login body (native, One Tap, email): string values only,\n * at most `MAX_ATTRIBUTES_BYTES` serialized. Sent by the client itself, so a bad one is the\n * client's error (400), unlike oversized attributes passed to `oauth2Authorization` by the host.\n */\nexport const attributesSchema = optional(\n record(string(), string()).check(\n refine((value) => attributesSize(value) <= MAX_ATTRIBUTES_BYTES, {\n message: `attributes must be at most ${MAX_ATTRIBUTES_BYTES} bytes`,\n })\n )\n);\n\n/**\n * Reads `attributes` off a login body without trusting its shape: `{}` when absent, `null` when\n * present but invalid.\n */\nexport function bodyAttributes(body: unknown): Record<string, string> | null {\n const value =\n typeof body === 'object' && body !== null && 'attributes' in body ? body.attributes : undefined;\n const parsed = attributesSchema.safeParse(value);\n if (!parsed.success) return null;\n return parsed.data ?? {};\n}\n"],"mappings":";;;AAGA,MAAa,uBAAuB;AAEpC,SAAgB,eAAe,YAAoC;CACjE,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,UAAU,CAAC,CAAC,CAAC;AAC9D;;;;;;AAOA,MAAa,mBAAmB,SAC9B,OAAO,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,MACzB,QAAQ,UAAU,eAAe,KAAK,KAAK,sBAAsB,EAC/D,SAAS,8BAA8B,qBAAqB,QAC9D,CAAC,CACH,CACF;;;;;AAMA,SAAgB,eAAe,MAA8C;CAC3E,MAAM,QACJ,OAAO,SAAS,YAAY,SAAS,QAAQ,gBAAgB,OAAO,KAAK,aAAa,KAAA;CACxF,MAAM,SAAS,iBAAiB,UAAU,KAAK;CAC/C,IAAI,CAAC,OAAO,SAAS,OAAO;CAC5B,OAAO,OAAO,QAAQ,CAAC;AACzB"}
|
package/dist/auth/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_auth_attributes = require("./attributes.cjs");
|
|
2
3
|
const require_email_schema = require("../email/schema.cjs");
|
|
3
4
|
const require_oauth2_client = require("../oauth2/client.cjs");
|
|
4
5
|
const require_oauth2_provider_google = require("../oauth2/provider/google.cjs");
|
|
@@ -11,6 +12,24 @@ let node_crypto = require("node:crypto");
|
|
|
11
12
|
let _shware_utils = require("@shware/utils");
|
|
12
13
|
//#region src/auth/index.ts
|
|
13
14
|
const PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;
|
|
15
|
+
function authorizationAttributes(attributes) {
|
|
16
|
+
if (!attributes) return void 0;
|
|
17
|
+
const defined = {};
|
|
18
|
+
for (const [key, value] of Object.entries(attributes)) if (value !== void 0) defined[key] = value;
|
|
19
|
+
if (Object.keys(defined).length === 0) return void 0;
|
|
20
|
+
const size = require_auth_attributes.attributesSize(defined);
|
|
21
|
+
(0, _shware_utils.invariant)(size <= require_auth_attributes.MAX_ATTRIBUTES_BYTES, `oauth2 authorization attributes are ${size} bytes, at most ${require_auth_attributes.MAX_ATTRIBUTES_BYTES} allowed`);
|
|
22
|
+
return defined;
|
|
23
|
+
}
|
|
24
|
+
/** Sign in with Apple answers with `response_mode=form_post`; a charset parameter may follow. */
|
|
25
|
+
const INVALID_ATTRIBUTES = {
|
|
26
|
+
error: "invalid_request",
|
|
27
|
+
error_description: `attributes must map strings to strings, at most ${require_auth_attributes.MAX_ATTRIBUTES_BYTES} bytes`
|
|
28
|
+
};
|
|
29
|
+
function isFormPost(request) {
|
|
30
|
+
const contentType = request.headers.get("content-type")?.toLowerCase();
|
|
31
|
+
return request.method === "POST" && !!contentType?.startsWith("application/x-www-form-urlencoded");
|
|
32
|
+
}
|
|
14
33
|
const PATH = {
|
|
15
34
|
CSRF: "/csrf",
|
|
16
35
|
LOGOUT: "/logout",
|
|
@@ -136,8 +155,9 @@ var Auth = class {
|
|
|
136
155
|
await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 600);
|
|
137
156
|
return Response.json({ nonce });
|
|
138
157
|
};
|
|
139
|
-
oauth2Authorization = async (request) => {
|
|
158
|
+
oauth2Authorization = async (request, options) => {
|
|
140
159
|
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
160
|
+
const attributes = authorizationAttributes(options?.attributes);
|
|
141
161
|
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
142
162
|
const { registrationId } = require_utils_http.param(request, PATH.OAUTH2_AUTHORIZATION);
|
|
143
163
|
const state = (0, node_crypto.randomUUID)();
|
|
@@ -154,7 +174,8 @@ var Auth = class {
|
|
|
154
174
|
state,
|
|
155
175
|
registrationId,
|
|
156
176
|
authorizationRequestUri: uri.href,
|
|
157
|
-
additionalParameters: pkce
|
|
177
|
+
additionalParameters: pkce,
|
|
178
|
+
...attributes ? { attributes } : {}
|
|
158
179
|
};
|
|
159
180
|
const value = JSON.stringify(authorizationRequest);
|
|
160
181
|
session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
|
|
@@ -191,7 +212,7 @@ var Auth = class {
|
|
|
191
212
|
const cached = JSON.parse(json);
|
|
192
213
|
const { registrationId } = require_utils_http.param(request, PATH.LOGIN_OAUTH2_CODE);
|
|
193
214
|
let data;
|
|
194
|
-
if (request
|
|
215
|
+
if (isFormPost(request)) {
|
|
195
216
|
const params = new URLSearchParams(await request.text());
|
|
196
217
|
data = Object.fromEntries(params.entries());
|
|
197
218
|
} else data = require_utils_http.query(request);
|
|
@@ -212,7 +233,12 @@ var Auth = class {
|
|
|
212
233
|
token
|
|
213
234
|
});
|
|
214
235
|
mark("exchange_code");
|
|
215
|
-
const principal = await handler(request,
|
|
236
|
+
const principal = await handler(request, {
|
|
237
|
+
registrationId,
|
|
238
|
+
userInfo,
|
|
239
|
+
token,
|
|
240
|
+
attributes: cached.attributes ?? {}
|
|
241
|
+
});
|
|
216
242
|
mark("save_principal");
|
|
217
243
|
session.setLastAccessedTime(Date.now());
|
|
218
244
|
session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
@@ -238,7 +264,10 @@ var Auth = class {
|
|
|
238
264
|
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
239
265
|
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
240
266
|
const { registrationId } = require_utils_http.param(request, PATH.LOGIN_OAUTH2_NATIVE);
|
|
241
|
-
const
|
|
267
|
+
const body = await request.json();
|
|
268
|
+
const attributes = require_auth_attributes.bodyAttributes(body);
|
|
269
|
+
if (!attributes) return Response.json(INVALID_ATTRIBUTES, { status: 400 });
|
|
270
|
+
const { attributes: _, ...credentials } = body;
|
|
242
271
|
const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({
|
|
243
272
|
registrationId,
|
|
244
273
|
credentials
|
|
@@ -253,7 +282,12 @@ var Auth = class {
|
|
|
253
282
|
await this.kv.removeItem(this.getOauth2NonceKey(nonce));
|
|
254
283
|
mark("check_nonce");
|
|
255
284
|
}
|
|
256
|
-
const principal = await handler(request,
|
|
285
|
+
const principal = await handler(request, {
|
|
286
|
+
registrationId,
|
|
287
|
+
userInfo,
|
|
288
|
+
token,
|
|
289
|
+
attributes
|
|
290
|
+
});
|
|
257
291
|
mark("save_principal");
|
|
258
292
|
const session = this.repository.createSession();
|
|
259
293
|
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
@@ -274,28 +308,43 @@ var Auth = class {
|
|
|
274
308
|
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
275
309
|
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
276
310
|
let body;
|
|
311
|
+
let attributes = {};
|
|
277
312
|
if (request.headers.get("Content-Type")?.includes("application/x-www-form-urlencoded")) {
|
|
278
313
|
const text = await request.text();
|
|
279
314
|
body = Object.fromEntries(new URLSearchParams(text));
|
|
280
|
-
} else if (request.headers.get("Content-Type")?.includes("application/json"))
|
|
281
|
-
|
|
315
|
+
} else if (request.headers.get("Content-Type")?.includes("application/json")) {
|
|
316
|
+
const json = await request.json();
|
|
317
|
+
attributes = require_auth_attributes.bodyAttributes(json);
|
|
318
|
+
body = json;
|
|
319
|
+
} else return Response.json({
|
|
282
320
|
error: "invalid_request",
|
|
283
321
|
error_description: "invalid content type"
|
|
284
322
|
}, { status: 400 });
|
|
323
|
+
if (!attributes) return Response.json(INVALID_ATTRIBUTES, { status: 400 });
|
|
285
324
|
const parsed = require_oauth2_client.googleOneTapSchema.parse(body);
|
|
286
325
|
let principal;
|
|
287
326
|
if (parsed.credential) {
|
|
288
327
|
const { token, userInfo } = await require_oauth2_provider_google.google.getTokenInfo(parsed.credential);
|
|
289
|
-
principal = await handler(request,
|
|
328
|
+
principal = await handler(request, {
|
|
329
|
+
registrationId,
|
|
330
|
+
userInfo,
|
|
331
|
+
token,
|
|
332
|
+
attributes
|
|
333
|
+
});
|
|
290
334
|
} else if (parsed.code) {
|
|
291
335
|
const token = await this.oauth2Client.exchangeAuthorizationCode({
|
|
292
336
|
registrationId,
|
|
293
337
|
code: parsed.code
|
|
294
338
|
});
|
|
295
|
-
principal = await handler(request,
|
|
339
|
+
principal = await handler(request, {
|
|
296
340
|
registrationId,
|
|
297
|
-
|
|
298
|
-
|
|
341
|
+
userInfo: await this.oauth2Client.getUserInfo({
|
|
342
|
+
registrationId,
|
|
343
|
+
token
|
|
344
|
+
}),
|
|
345
|
+
token,
|
|
346
|
+
attributes
|
|
347
|
+
});
|
|
299
348
|
} else throw new Error("invalid request");
|
|
300
349
|
mark("save_principal");
|
|
301
350
|
const session = this.repository.createSession();
|
|
@@ -336,18 +385,22 @@ var Auth = class {
|
|
|
336
385
|
loginEmail = async (request, handler) => {
|
|
337
386
|
const { data, error } = require_utils_valid.valid(require_email_schema.loginEmailSchema, await request.json());
|
|
338
387
|
if (error) return Response.json(error, { status: error.error.code });
|
|
339
|
-
const { email, verificationCode } = data;
|
|
388
|
+
const { email, verificationCode, attributes } = data;
|
|
340
389
|
const key = this.getEmailVerificationCodeKey(email);
|
|
341
390
|
const stored = await this.kv.getItem(key);
|
|
342
391
|
if (typeof stored !== "string" || stored.length !== verificationCode.length || !(0, node_crypto.timingSafeEqual)(Buffer.from(stored), Buffer.from(verificationCode))) return Response.json(require_utils_valid.invalidArgument("INVALID_VERIFICATION_CODE"), { status: 400 });
|
|
343
392
|
await this.kv.removeItem(key);
|
|
344
|
-
const principal = await handler(request,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
393
|
+
const principal = await handler(request, {
|
|
394
|
+
registrationId: "EMAIL",
|
|
395
|
+
userInfo: {
|
|
396
|
+
claims: {
|
|
397
|
+
sub: email,
|
|
398
|
+
email,
|
|
399
|
+
email_verified: true
|
|
400
|
+
},
|
|
401
|
+
data: {}
|
|
349
402
|
},
|
|
350
|
-
|
|
403
|
+
attributes: attributes ?? {}
|
|
351
404
|
});
|
|
352
405
|
const session = this.repository.createSession();
|
|
353
406
|
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
package/dist/auth/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["OAuth2Client","getCookie","randomUUID","PRINCIPAL_NAME_INDEX_NAME","createHash","param","timing","query","oauth2RedirectQuerySchema","googleOneTapSchema","google","valid","sendEmailVerificationCodeSchema","verifyTurnstileToken","randomInt","invalidArgument","loginEmailSchema","timingSafeEqual"],"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport type { Principal } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport type { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private readonly redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredential;\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, registrationId, userInfo, token);\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n let body: Record<string, string>;\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n body = (await request.json()) as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, registrationId, userInfo, token);\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, registrationId, userInfo, token);\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, 'EMAIL', { claims, data: {} });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["attributesSize","MAX_ATTRIBUTES_BYTES","OAuth2Client","getCookie","randomUUID","PRINCIPAL_NAME_INDEX_NAME","createHash","param","timing","query","oauth2RedirectQuerySchema","bodyAttributes","googleOneTapSchema","google","valid","sendEmailVerificationCodeSchema","verifyTurnstileToken","randomInt","invalidArgument","loginEmailSchema","timingSafeEqual"],"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomInt, randomUUID, timingSafeEqual } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport type { Principal } from '../core/index';\nimport { loginEmailSchema, sendEmailVerificationCodeSchema } from '../email/schema';\nimport { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2ErrorType } from '../oauth2/error';\nimport { google } from '../oauth2/provider/index';\nimport type {\n NativeCredential,\n OAuth2AuthorizationRequest,\n PkceParameters,\n StandardClaims,\n} from '../oauth2/types';\nimport { PRINCIPAL_NAME_INDEX_NAME } from '../session/common';\nimport type { KVRepository, Session, SessionRepository } from '../session/types';\nimport {\n type CookieOptions,\n deleteCookie,\n getCookie,\n param,\n query,\n setCookie,\n} from '../utils/http';\nimport { timing } from '../utils/timing';\nimport { invalidArgument, valid, verifyTurnstileToken } from '../utils/valid';\nimport { MAX_ATTRIBUTES_BYTES, attributesSize, bodyAttributes } from './attributes';\nimport type {\n AuthConfig,\n AuthService,\n AuthorizedHandler,\n LoggedHandler,\n OAuth2AuthorizationOptions,\n} from './types';\n\n// PKCE per RFC 7636: code_verifier / code_challenge are 43-128 chars from\n// the unreserved set. We restrict to base64url (drops \".\" and \"~\") since\n// that's what S256 produces and what callers should be sending; rejecting\n// the rest shrinks the accepted input shape without losing legit clients.\n// A 32-byte random verifier base64url-encodes to exactly 43 chars.\nconst PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;\n\nfunction authorizationAttributes(\n attributes: OAuth2AuthorizationOptions['attributes']\n): Record<string, string> | undefined {\n if (!attributes) return undefined;\n const defined: Record<string, string> = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined) defined[key] = value;\n }\n if (Object.keys(defined).length === 0) return undefined;\n // The host's own values, written with the session: oversized is a bug in the host, not a 400.\n const size = attributesSize(defined);\n invariant(\n size <= MAX_ATTRIBUTES_BYTES,\n `oauth2 authorization attributes are ${size} bytes, at most ${MAX_ATTRIBUTES_BYTES} allowed`\n );\n return defined;\n}\n\n/** Sign in with Apple answers with `response_mode=form_post`; a charset parameter may follow. */\nconst INVALID_ATTRIBUTES = {\n error: 'invalid_request',\n error_description: `attributes must map strings to strings, at most ${MAX_ATTRIBUTES_BYTES} bytes`,\n};\n\nfunction isFormPost(request: Request) {\n const contentType = request.headers.get('content-type')?.toLowerCase();\n return (\n request.method === 'POST' && !!contentType?.startsWith('application/x-www-form-urlencoded')\n );\n}\n\ninterface DesktopCodeRecord {\n /** principal.name from the upstream cookie session */\n name: string;\n /** code_challenge (S256, base64url) — verifier must hash to this */\n cc: string;\n}\n\nexport const PATH = {\n CSRF: '/csrf',\n LOGOUT: '/logout',\n LOGGED: '/logged',\n\n OAUTH2_STATE: '/oauth2/state/:registrationId',\n OAUTH2_NONCE: '/oauth2/nonce/:registrationId',\n OAUTH2_AUTHORIZATION: '/oauth2/authorization/:registrationId',\n LOGIN_OAUTH2_CODE: '/login/oauth2/code/:registrationId',\n LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:registrationId',\n LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google',\n\n LOGIN_EMAIL: '/login/email',\n LOGIN_PHONE: '/login/phone',\n SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode',\n SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode',\n\n // Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):\n // 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }\n // 2. web redirects to desktop's loopback callback with the code\n // 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns\n // Set-Cookie carrying a fresh session, single-use code is invalidated\n // The two-step flow keeps the actual session id off the redirect URL — only a\n // 5-minute single-use code transits via the loopback.\n DESKTOP_AUTHORIZE: '/auth/desktop/authorize',\n DESKTOP_EXCHANGE: '/auth/desktop/exchange',\n\n CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup',\n} as const;\n\nexport class Auth implements AuthService {\n private readonly timing: boolean;\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly kv: KVRepository;\n private readonly repository: SessionRepository;\n private readonly turnstileSecretKey?: string;\n private readonly oauth2Client: OAuth2Client | null;\n private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({\n sessionRepository,\n kvRepository,\n turnstileSecretKey,\n oauth2,\n cookie,\n timing,\n }: AuthConfig) {\n this.timing = timing ?? false;\n this.kv = kvRepository;\n this.repository = sessionRepository;\n this.turnstileSecretKey = turnstileSecretKey;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n private getSessionId(request: Request): string | undefined {\n let sessionId: string | undefined;\n const header = request.headers.get('Authorization');\n if (header?.startsWith('Bearer ')) {\n sessionId = header.replace('Bearer ', '');\n }\n if (!sessionId) {\n sessionId = getCookie(request, this.cookieName);\n }\n return sessionId;\n }\n\n csrf = async (_request: Request): Promise<Response> => {\n const token = randomUUID();\n const response = Response.json({\n token,\n parameterName: '_csrf',\n headerName: 'X-XSRF-TOKEN',\n });\n\n // Double Submit Cookie Protection\n setCookie(response, 'XSRF-TOKEN', token, {\n path: '/',\n secure: true,\n httpOnly: false,\n sameSite: 'none',\n domain: this.cookieOptions.domain,\n });\n return response;\n };\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = this.getSessionId(request);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n private getOauth2NonceKey(nonce: string) {\n return `oauth2:nonce:${nonce}`;\n }\n\n private getEmailVerificationCodeKey(email: string) {\n return `email:verification:${email}`;\n }\n\n private getPhoneVerificationCodeKey(phone: string) {\n return `phone:verification:${phone}`;\n }\n\n private getDesktopCodeKey(code: string) {\n return `desktop:code:${code}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_STATE);\n const state = randomUUID();\n await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 10 * 60);\n return Response.json({ state });\n };\n\n oauth2Nonce = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, PATH.OAUTH2_NONCE);\n const nonce = randomUUID();\n await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 10 * 60);\n return Response.json({ nonce });\n };\n\n oauth2Authorization = async (\n request: Request,\n options?: OAuth2AuthorizationOptions\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const attributes = authorizationAttributes(options?.attributes);\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = await this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = this.getSessionId(request);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n mark('fetch_session');\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n ...(attributes ? { attributes } : {}),\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n mark('save_session');\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n private readonly redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // 1. get session from header or cookie\n const sessionId = this.getSessionId(request);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n mark('load_session');\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (isFormPost(request)) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n mark('validate_session');\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n mark('exchange_code');\n\n // 5. create or update principal, with what the host attached when the flow started\n const principal = await handler(request, {\n registrationId,\n userInfo,\n token,\n attributes: cached.attributes ?? {},\n });\n mark('save_principal');\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n mark('update_session');\n\n const uri = new URL(this.oauth2Client.successUri);\n uri.searchParams.set('status', 'success');\n uri.searchParams.set('registrationId', registrationId);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Native = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n const { registrationId } = param(request, PATH.LOGIN_OAUTH2_NATIVE);\n const body: unknown = await request.json();\n // Whatever the app sends besides its credentials for `onAuthorized` (a referral code, ...).\n const attributes = bodyAttributes(body);\n if (!attributes) return Response.json(INVALID_ATTRIBUTES, { status: 400 });\n // Only the credentials go on to the provider client.\n const { attributes: _, ...credentials } = body as NativeCredential & { attributes?: unknown };\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n });\n mark('login_oauth2_native');\n\n const { nonce } = userInfo.data as { nonce?: string };\n if (nonce) {\n const value = await this.kv.getItem(this.getOauth2NonceKey(nonce));\n if (value !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'nonce mismatch' };\n return Response.json(json, { status: 400 });\n }\n await this.kv.removeItem(this.getOauth2NonceKey(nonce));\n mark('check_nonce');\n }\n\n const principal = await handler(request, { registrationId, userInfo, token, attributes });\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n loginOAuth2Onetap = async (\n request: Request,\n handler: AuthorizedHandler,\n registrationId = 'google'\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { mark, setTiming } = timing({ enabled: this.timing });\n\n // should support application/x-www-form-urlencoded and application/json\n // Google's own form post carries no attributes; an app posting JSON may add them.\n let body: Record<string, string>;\n let attributes: Record<string, string> | null = {};\n if (request.headers.get('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n const text = await request.text();\n body = Object.fromEntries(new URLSearchParams(text));\n } else if (request.headers.get('Content-Type')?.includes('application/json')) {\n const json: unknown = await request.json();\n attributes = bodyAttributes(json);\n body = json as Record<string, string>;\n } else {\n const json = { error: 'invalid_request', error_description: 'invalid content type' };\n return Response.json(json, { status: 400 });\n }\n\n if (!attributes) return Response.json(INVALID_ATTRIBUTES, { status: 400 });\n const parsed = googleOneTapSchema.parse(body);\n\n // csrf token is not supported in FedCM mode\n // const csrf_cookie = getCookie(request, 'g_csrf_token');\n // const csrf_request = parsed.g_csrf_token;\n // if (!csrf_request || csrf_cookie !== csrf_request) {\n // const json = { error: 'invalid_request', error_description: 'invalid csrf token' };\n // return Response.json(json, { status: 400 });\n // }\n\n let principal: Principal;\n if (parsed.credential) {\n const { token, userInfo } = await google.getTokenInfo(parsed.credential);\n principal = await handler(request, { registrationId, userInfo, token, attributes });\n } else if (parsed.code) {\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code: parsed.code,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n principal = await handler(request, { registrationId, userInfo, token, attributes });\n } else {\n throw new Error('invalid request');\n }\n\n mark('save_principal');\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n setTiming(response);\n\n return response;\n };\n\n sendEmailVerificationCode = async (\n request: Request,\n sender: (data: { email: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n invariant(this.turnstileSecretKey, 'turnstileSecretKey is not configured');\n\n const { data, error } = valid(sendEmailVerificationCodeSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, turnstileToken } = data;\n\n const result = await verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);\n if (result.error) return Response.json(result.error, { status: result.error.error.code });\n\n try {\n const verificationCode = randomInt(1_000_000).toString().padStart(6, '0');\n await sender({ email, verificationCode });\n await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 10 * 60);\n return Response.json({});\n } catch (e) {\n console.error('Failed to send email verification code', e);\n return Response.json(invalidArgument('EMAIL_DELIVERY_FAILED'), { status: 400 });\n }\n };\n\n loginEmail = async (request: Request, handler: AuthorizedHandler): Promise<Response> => {\n const { data, error } = valid(loginEmailSchema, await request.json());\n if (error) return Response.json(error, { status: error.error.code });\n const { email, verificationCode, attributes } = data;\n\n const key = this.getEmailVerificationCodeKey(email);\n const stored = await this.kv.getItem(key);\n if (\n typeof stored !== 'string' ||\n stored.length !== verificationCode.length ||\n !timingSafeEqual(Buffer.from(stored), Buffer.from(verificationCode))\n ) {\n return Response.json(invalidArgument('INVALID_VERIFICATION_CODE'), { status: 400 });\n }\n await this.kv.removeItem(key);\n const claims: StandardClaims = { sub: email, email, email_verified: true };\n const principal = await handler(request, {\n registrationId: 'EMAIL',\n userInfo: { claims, data: {} },\n attributes: attributes ?? {},\n });\n\n // save session\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n /**\n * Step 1 of the desktop / native app sign-in handshake. The caller already\n * completed normal sign-in on the web (so a cookie session exists); this\n * issues a 5-minute single-use code that the web page hands off to the\n * desktop client via a loopback redirect.\n *\n * PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`\n * before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,\n * and threads the challenge through the URL → web page → here. We pin\n * the challenge to the code in KV; `desktopExchange` enforces that the\n * caller knows the matching verifier. This blocks an attacker who only\n * intercepts the auth code (browser history, malicious extension, packet\n * capture on loopback) from exchanging it.\n *\n * Requires an authenticated cookie session — anonymous callers get 401.\n * Throttling / abuse protection on this endpoint is the API layer's job\n * (rate-limit per principal); the SDK doesn't gate it because the\n * upstream sign-in flow already passed CAPTCHA / OAuth / verification.\n */\n desktopAuthorize = async (request: Request): Promise<Response> => {\n const principal = await this.getPrincipal(request);\n if (!principal) {\n const body = {\n error: {\n code: 401,\n status: 'UNAUTHENTICATED',\n message: 'sign in first',\n details: [],\n },\n };\n return Response.json(body, { status: 401 });\n }\n\n let body: { code_challenge?: unknown; code_challenge_method?: unknown };\n try {\n body = (await request.json()) as {\n code_challenge?: unknown;\n code_challenge_method?: unknown;\n };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const codeChallenge = typeof body.code_challenge === 'string' ? body.code_challenge : null;\n if (\n !codeChallenge ||\n body.code_challenge_method !== 'S256' ||\n !PKCE_FORMAT.test(codeChallenge)\n ) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = randomUUID();\n const record: DesktopCodeRecord = { name: principal.name, cc: codeChallenge };\n await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 5 * 60);\n\n return Response.json({ code });\n };\n\n /**\n * Step 2 of the desktop / native app sign-in handshake. The desktop\n * client hands back the auth code it received via the loopback callback,\n * and we mint a fresh session for it — independent of the web cookie\n * session that originally `authorize`'d the code, so logging out on\n * either side doesn't cascade to the other.\n *\n * Cookie attributes: defaults to `SameSite=None; Secure` so the cookie\n * survives cross-site fetches from the desktop renderer's `file://` /\n * `app://` origin. The configured base `cookieOptions` provide\n * everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override\n * is targeted at the SameSite/Secure pair only. In a non-HTTPS dev\n * backend this combination is rejected by Chromium — see the desktop\n * sign-in PRD for workarounds.\n *\n * No CSRF check needed — the request carries no cookie, the auth code\n * itself is the credential, and KV's single-use removal blocks replay.\n */\n desktopExchange = async (request: Request): Promise<Response> => {\n let body: { code?: unknown; code_verifier?: unknown };\n try {\n body = (await request.json()) as { code?: unknown; code_verifier?: unknown };\n } catch {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const code = typeof body.code === 'string' ? body.code : null;\n const verifier = typeof body.code_verifier === 'string' ? body.code_verifier : null;\n if (!code || !verifier || !PKCE_FORMAT.test(verifier)) {\n return Response.json(invalidArgument('INVALID_ARGUMENT'), { status: 400 });\n }\n\n const key = this.getDesktopCodeKey(code);\n const raw = await this.kv.getItem(key);\n if (typeof raw !== 'string') {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n let record: DesktopCodeRecord;\n try {\n record = JSON.parse(raw) as DesktopCodeRecord;\n } catch {\n // Malformed KV record (shouldn't happen with our own writes) — drop it\n // and surface as an invalid code; the caller can retry the whole flow.\n await this.kv.removeItem(key);\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // Verify PKCE: base64url(sha256(verifier)) === stored challenge.\n // Don't burn the code on a verifier mismatch — that would let an\n // attacker who races a network retry brick a legit exchange. The code\n // is single-use on success and 5-min TTL bounded; brute-forcing the\n // 256-bit verifier in that window is infeasible.\n const computed = createHash('sha256').update(verifier).digest();\n const stored = Buffer.from(record.cc, 'base64url');\n if (stored.length !== computed.length || !timingSafeEqual(computed, stored)) {\n return Response.json(invalidArgument('INVALID_DESKTOP_CODE'), { status: 400 });\n }\n\n // One-time use: remove BEFORE creating the session so a concurrent\n // duplicate exchange (e.g. retry after timeout) sees the code as\n // consumed even if our session save races it.\n await this.kv.removeItem(key);\n\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, record.name);\n session.setAttribute('client_type', 'desktop');\n await this.repository.save(session);\n\n const response = Response.json({ ok: true });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), {\n ...this.cookieOptions,\n // Override sameSite/secure for desktop: the renderer's origin\n // (file:// / app://) is cross-site to the API, so SameSite=Lax/Strict\n // would block the cookie on subsequent fetches.\n sameSite: 'none',\n secure: true,\n maxAge,\n });\n return response;\n };\n\n sendPhoneVerificationCode = async (\n _request: Request,\n _sender: (data: { phone: string; verificationCode: string }) => Promise<void>\n ): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n loginPhone = async (_request: Request, _handler: AuthorizedHandler): Promise<Response> => {\n return Response.json({ error: 'not_implemented' }, { status: 501 });\n };\n\n kick = async (principal: Principal): Promise<void> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n for (const sessionId of sessions.keys()) {\n await this.repository.deleteById(sessionId);\n }\n };\n\n isAuthenticated = async (request: Request): Promise<boolean> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return false;\n const session = await this.repository.findById(sessionId);\n if (!session) return false;\n const principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!principalName) return false;\n return true;\n };\n\n getSession = async <T extends boolean>(\n request: Request,\n create: T\n ): Promise<T extends true ? Session : Session | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n const session = await this.repository.findById(sessionId);\n if (!session) {\n if (create) {\n const session = this.repository.createSession();\n await this.repository.save(session);\n return session;\n } else {\n return null as T extends true ? Session : Session | null;\n }\n }\n return session;\n };\n\n deleteSession = async (sessionId: string): Promise<void> => {\n await this.repository.deleteById(sessionId);\n };\n\n getPrincipal = async (request: Request): Promise<Principal | null> => {\n const sessionId = this.getSessionId(request);\n if (!sessionId) return null;\n const session = await this.repository.findById(sessionId);\n if (!session) return null;\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n if (!name) return null;\n return { name: String(name) };\n };\n\n listSessions = async (principal: Principal): Promise<Session[]> => {\n const sessions = await this.repository.findByPrincipalName(principal.name);\n return Array.from(sessions.values());\n };\n\n cleanupExpiredSessions = async (cleanupCount?: number): Promise<Response> => {\n await this.repository.cleanupExpiredSessions(cleanupCount);\n return Response.json({});\n };\n}\n"],"mappings":"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"}
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Principal } from "../core/index.cjs";
|
|
2
2
|
import { Session } from "../session/types.cjs";
|
|
3
|
-
import { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler } from "./types.cjs";
|
|
3
|
+
import { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler, OAuth2AuthorizationOptions } from "./types.cjs";
|
|
4
4
|
//#region src/auth/index.d.ts
|
|
5
5
|
export declare const PATH: {
|
|
6
6
|
readonly CSRF: '/csrf';
|
|
@@ -42,7 +42,7 @@ export declare class Auth implements AuthService {
|
|
|
42
42
|
private getDesktopCodeKey;
|
|
43
43
|
oauth2State: (request: Request) => Promise<Response>;
|
|
44
44
|
oauth2Nonce: (request: Request) => Promise<Response>;
|
|
45
|
-
oauth2Authorization: (request: Request) => Promise<Response>;
|
|
45
|
+
oauth2Authorization: (request: Request, options?: OAuth2AuthorizationOptions) => Promise<Response>;
|
|
46
46
|
private readonly redirect;
|
|
47
47
|
loginOAuth2Code: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
|
|
48
48
|
loginOAuth2Native: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/auth/index.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/auth/index.ts"],"mappings":";;;;qBA+Ea;WACX;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WACA;WACA;WASA;WACA;WAEA;;qBAGW,gBAAgB;mBACV;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EAEjB,cACE,mBACA,cACA,oBACA,QACA,QACA,UACC;UAiBK;EAYR,OAAI,UAAoB,YAAU,QAAQ;EAmB1C,SAAM,SAAmB,YAAU,QAAQ;EAQ3C,SAAM,SAAmB,SAAO,WAAa,kBAAgB,QAAQ;UAsB7D;UAUA;UAIA;UAIA;UAIA;UAIA;EAIR,cAAW,SAAmB,YAAU,QAAQ;EAOhD,cAAW,SAAmB,YAAU,QAAQ;EAOhD,sBAAmB,SACR,SAAO,UACN,+BACT,QAAQ;mBAmCM;EAQjB,kBAAe,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;EAoFhF,oBAAiB,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;EA4ClF,oBAAiB,SACN,SAAO,SACP,mBAAiB,4BAEzB,QAAQ;EA8DX,4BAAyB,SACd,SAAO,SACP;IAAQ;IAAe;QAA+B,kBAC9D,QAAQ;EAqBX,aAAU,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;;;;;;;;;;;;;;;;;;;;EAuD3E,mBAAgB,SAAmB,YAAU,QAAQ;;;;;;;;;;;;;;;;;;;EA0DrD,kBAAe,SAAmB,YAAU,QAAQ;EAgEpD,4BAAyB,UACb,SAAO,UACP;IAAQ;IAAe;QAA+B,kBAC/D,QAAQ;EAIX,aAAU,UAAoB,SAAO,UAAY,sBAAoB,QAAQ;EAI7E,OAAI,WAAqB,cAAY;EAOrC,kBAAe,SAAmB,YAAU;EAU5C,aAAoB,mBAAiB,SAC1B,SAAO,QACR,MACP,QAAQ,iBAAiB,UAAU;EAwBtC,gBAAa,sBAA8B;EAI3C,eAAY,SAAmB,YAAU,QAAQ;EAUjD,eAAY,WAAqB,cAAY,QAAQ;EAKrD,yBAAsB,0BAAkC,QAAQ"}
|