@growth-labs/auth 0.5.1 → 0.5.2
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 +7 -2
- package/dist/routes/api/shared.d.ts.map +1 -1
- package/dist/routes/api/shared.js +21 -6
- package/dist/routes/api/shared.js.map +1 -1
- package/dist/routes/flow.d.ts +2 -1
- package/dist/routes/flow.d.ts.map +1 -1
- package/dist/routes/flow.js +5 -2
- package/dist/routes/flow.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ auth({
|
|
|
58
58
|
- `GET /api/auth/callback` — OAuth callback handler (PKCE code exchange, cookie setting)
|
|
59
59
|
- `GET /forgot-password` / `GET /reset-password` — local redirects to issuer-owned password UI unless renderers are configured
|
|
60
60
|
- `GET /password` / `GET /verify-code` — local branded password and magic-code views when `renderers.password` / `renderers.verifyCode` are configured
|
|
61
|
-
- `POST /api/auth/password/verify`, `/api/auth/code/request`, `/api/auth/code/verify`, `/api/auth/password/reset/request`, `/api/auth/password/reset/confirm` — package-owned back-channel form handlers that call the issuer JSON API through `issuerInternal`
|
|
61
|
+
- `POST /api/auth/password/verify`, `/api/auth/code/request`, `/api/auth/code/verify`, `/api/auth/password/reset/request`, `/api/auth/password/reset/confirm` — package-owned back-channel form handlers that call the issuer JSON API through `issuerInternal`, then return a `303` browser redirect or a JSON redirect contract for `Accept: application/json` requests
|
|
62
62
|
|
|
63
63
|
## Branded Auth UI
|
|
64
64
|
|
|
@@ -80,6 +80,11 @@ Forgot/reset renderers receive both the historical issuer URL and the new
|
|
|
80
80
|
local API form target. Use `passwordResetRequestUrl` and
|
|
81
81
|
`passwordResetConfirmUrl` for consumer-hosted reset forms.
|
|
82
82
|
|
|
83
|
+
Package-owned POST routes are safe for native forms and fetch-enhanced
|
|
84
|
+
renderers. Native forms receive `303 See Other` redirects. Fetch callers should
|
|
85
|
+
send `Accept: application/json`; success returns `{ ok: true, redirect }`, and
|
|
86
|
+
errors return `{ error, redirect }` with a `400` status.
|
|
87
|
+
|
|
83
88
|
## Server SDK
|
|
84
89
|
|
|
85
90
|
```ts
|
|
@@ -157,7 +162,7 @@ internal package-test helper and is not exported from
|
|
|
157
162
|
|
|
158
163
|
1. User clicks a provider on the local login page → browser hits `/login?provider=...` or a local branded `/password` / `/verify-code` route when that renderer is configured
|
|
159
164
|
2. The local `/login` start route stores PKCE/state/redirect/provider cookies and redirects to `{issuer}/authorize?provider=...`; configured `resource` is sent as the RFC 8707 resource indicator. Consumer-hosted `/password` and `/verify-code` pages seed the same flow cookies locally, with the PKCE cookie visible to their package-owned POST handlers.
|
|
160
|
-
3. For consumer-hosted password or magic-code flows, package-owned POST routes call `{issuerInternal ?? issuer}/api/v1/auth/*` and redirect to `/api/auth/callback?code=...&state
|
|
165
|
+
3. For consumer-hosted password or magic-code flows, package-owned POST routes call `{issuerInternal ?? issuer}/api/v1/auth/*` and redirect to `/api/auth/callback?code=...&state=...`; native forms receive `303` redirects, while `Accept: application/json` callers receive `{ ok/error, redirect }`
|
|
161
166
|
4. Issuer authenticates or issues a JSON authorization code → callback exchanges code at `{issuerInternal ?? issuer}/token` via PKCE, sends `Authorization: Basic base64(client_id:client_secret)` when `clientSecret` is configured, includes `resource`, reads `properties.isNewUser` from the JWT payload with fallback to top-level `isNewUser` for legacy issuers, uses the `{cookiePrefix}_provider` auth-flow cookie set at login start for provider attribution, sets session cookies, sets `gl_id` when `GL_IDENTITY_SECRET` is configured, and redirects to the original page
|
|
162
167
|
|
|
163
168
|
## Signed Identity Cookie
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,KAAK,YAAY,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAoB,UAAU,EAAE,MAAM,YAAY,CAAA;AAEtF,wBAAsB,cAAc,CACnC,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,oBAAoB,EAC5B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAC7C,QAAQ,CAoBV;AAED,wBAAgB,cAAc,CAC7B,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACjD,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAC7C,QAAQ,CAKV;AAyBD,YAAY,EAAE,YAAY,EAAE,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA"}
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
import { getConfig } from '../../config-store.js';
|
|
2
2
|
import { authClient } from '../../server.js';
|
|
3
3
|
import { getCookie } from '../../utils/cookies.js';
|
|
4
|
-
import { readForm, redirectTo } from '../flow.js';
|
|
4
|
+
import { readForm, redirectLocation, redirectTo } from '../flow.js';
|
|
5
5
|
export async function readFormString(form, key, options = {}) {
|
|
6
6
|
const value = form.get(key)?.toString() ?? '';
|
|
7
7
|
return options.trim === false ? value : value.trim();
|
|
8
8
|
}
|
|
9
9
|
export function callbackRedirect(context, result, errorPath, errorParams) {
|
|
10
10
|
if ('error' in result) {
|
|
11
|
-
return
|
|
11
|
+
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error);
|
|
12
12
|
}
|
|
13
13
|
const options = getConfig();
|
|
14
14
|
const state = getCookie(context.request, `${options.cookiePrefix}_state`);
|
|
15
15
|
if (!state) {
|
|
16
|
-
return
|
|
16
|
+
return routeResult(context, errorPath, { ...errorParams, error: 'missing_auth_flow' }, 'missing_auth_flow');
|
|
17
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return routeResult(context, options.callbackPath, {
|
|
19
19
|
code: result.code,
|
|
20
20
|
state,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
export function statusRedirect(context, result, successPath, successParams, errorPath, errorParams) {
|
|
24
24
|
if ('error' in result) {
|
|
25
|
-
return
|
|
25
|
+
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error);
|
|
26
26
|
}
|
|
27
|
-
return
|
|
27
|
+
return routeResult(context, successPath, successParams);
|
|
28
|
+
}
|
|
29
|
+
function wantsJson(context) {
|
|
30
|
+
const accept = context.request.headers.get('accept') ?? '';
|
|
31
|
+
return accept
|
|
32
|
+
.split(',')
|
|
33
|
+
.some((value) => value.trim().toLowerCase().split(';')[0] === 'application/json');
|
|
34
|
+
}
|
|
35
|
+
function routeResult(context, pathname, params, error) {
|
|
36
|
+
const location = redirectLocation(context, pathname, params);
|
|
37
|
+
if (!wantsJson(context)) {
|
|
38
|
+
return redirectTo(context, pathname, params);
|
|
39
|
+
}
|
|
40
|
+
return Response.json(error ? { error, redirect: location } : { ok: true, redirect: location }, {
|
|
41
|
+
status: error ? 400 : 200,
|
|
42
|
+
});
|
|
28
43
|
}
|
|
29
44
|
export { authClient, getConfig, readForm, redirectTo };
|
|
30
45
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAsD,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAqB,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/flow.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface RouteContext {
|
|
|
4
4
|
url: URL;
|
|
5
5
|
}
|
|
6
6
|
export declare function withAuthFlowCookies(context: RouteContext, options: ResolvedAuthOptions, response: Response, redirectPath: string, provider: string): Promise<Response>;
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function redirectLocation(context: RouteContext, pathname: string, params: Record<string, string | undefined>): string;
|
|
8
|
+
export declare function redirectTo(context: RouteContext, pathname: string, params: Record<string, string | undefined>, status?: number): Response;
|
|
8
9
|
export declare function readForm(context: RouteContext): Promise<FormData>;
|
|
9
10
|
//# sourceMappingURL=flow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/routes/flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAIxD,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED,wBAAgB,UAAU,CACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/routes/flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAIxD,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;CACR;AAED,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACxC,MAAM,CAMR;AAED,wBAAgB,UAAU,CACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1C,MAAM,SAAM,GACV,QAAQ,CAEV;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEvE"}
|
package/dist/routes/flow.js
CHANGED
|
@@ -12,13 +12,16 @@ export async function withAuthFlowCookies(context, options, response, redirectPa
|
|
|
12
12
|
});
|
|
13
13
|
return mutable;
|
|
14
14
|
}
|
|
15
|
-
export function
|
|
15
|
+
export function redirectLocation(context, pathname, params) {
|
|
16
16
|
const target = new URL(pathname, context.url.origin);
|
|
17
17
|
for (const [key, value] of Object.entries(params)) {
|
|
18
18
|
if (value)
|
|
19
19
|
target.searchParams.set(key, value);
|
|
20
20
|
}
|
|
21
|
-
return
|
|
21
|
+
return target.toString();
|
|
22
|
+
}
|
|
23
|
+
export function redirectTo(context, pathname, params, status = 303) {
|
|
24
|
+
return Response.redirect(redirectLocation(context, pathname, params), status);
|
|
22
25
|
}
|
|
23
26
|
export async function readForm(context) {
|
|
24
27
|
return context.request.formData();
|
package/dist/routes/flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../../src/routes/flow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAOrD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAAqB,EACrB,OAA4B,EAC5B,QAAkB,EAClB,YAAoB,EACpB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAA;IAC9C,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC3C,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;KACtC,CAAC,CAAA;IACF,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE;QAC/F,QAAQ,EAAE,GAAG;KACb,CAAC,CAAA;IACF,OAAO,OAAO,CAAA;AACf,CAAC;AAED,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../../src/routes/flow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAOrD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAAqB,EACrB,OAA4B,EAC5B,QAAkB,EAClB,YAAoB,EACpB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAA;IAC9C,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC3C,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;KACtC,CAAC,CAAA;IACF,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE;QAC/F,QAAQ,EAAE,GAAG;KACb,CAAC,CAAA;IACF,OAAO,OAAO,CAAA;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC/B,OAAqB,EACrB,QAAgB,EAChB,MAA0C;IAE1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK;YAAE,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC/C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,UAAU,CACzB,OAAqB,EACrB,QAAgB,EAChB,MAA0C,EAC1C,MAAM,GAAG,GAAG;IAEZ,OAAO,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAA;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB;IACnD,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;AAClC,CAAC"}
|