@growth-labs/auth 0.7.3 → 0.7.5
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 +20 -15
- package/dist/routes/api/code-request.d.ts.map +1 -1
- package/dist/routes/api/code-request.js +1 -1
- package/dist/routes/api/code-request.js.map +1 -1
- package/dist/routes/api/password-reset-confirm.d.ts.map +1 -1
- package/dist/routes/api/password-reset-confirm.js +1 -1
- package/dist/routes/api/password-reset-confirm.js.map +1 -1
- package/dist/routes/api/password-reset-request.d.ts.map +1 -1
- package/dist/routes/api/password-reset-request.js +1 -1
- package/dist/routes/api/password-reset-request.js.map +1 -1
- package/dist/routes/api/shared.d.ts +3 -2
- package/dist/routes/api/shared.d.ts.map +1 -1
- package/dist/routes/api/shared.js +61 -2
- package/dist/routes/api/shared.js.map +1 -1
- package/dist/routes/callback.d.ts.map +1 -1
- package/dist/routes/callback.js +44 -7
- package/dist/routes/callback.js.map +1 -1
- package/dist/routes/flow.d.ts +6 -0
- package/dist/routes/flow.d.ts.map +1 -1
- package/dist/routes/flow.js.map +1 -1
- package/dist/utils/analytics.d.ts +17 -0
- package/dist/utils/analytics.d.ts.map +1 -1
- package/dist/utils/analytics.js +53 -33
- package/dist/utils/analytics.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -8
- package/src/routes/api/code-request.ts +1 -0
- package/src/routes/api/password-reset-confirm.ts +1 -0
- package/src/routes/api/password-reset-request.ts +1 -0
- package/src/routes/api/shared.ts +100 -2
- package/src/routes/callback.ts +65 -11
- package/src/routes/flow.ts +6 -0
- package/src/utils/analytics.ts +80 -32
- package/src/utils/index.ts +12 -1
package/README.md
CHANGED
|
@@ -114,9 +114,10 @@ consumers can also use it from custom server routes.
|
|
|
114
114
|
|
|
115
115
|
## Analytics Events
|
|
116
116
|
|
|
117
|
-
When `@growth-labs/analytics@0.4.0` or newer is installed,
|
|
118
|
-
|
|
119
|
-
`trackServerEvent` from
|
|
117
|
+
When `@growth-labs/analytics@0.4.0` or newer is installed, successful
|
|
118
|
+
callback and JSON verify routes emit `signup_completed` or
|
|
119
|
+
`login_completed` through `trackServerEvent` from
|
|
120
|
+
`@growth-labs/analytics/utils`. The payload is:
|
|
120
121
|
|
|
121
122
|
```ts
|
|
122
123
|
{
|
|
@@ -132,6 +133,10 @@ can preserve the site's anonymous `visitor_id`, `session_id`, attribution
|
|
|
132
133
|
cookies, and `gl_identity_links` row. The identity platform does not need
|
|
133
134
|
to see site attribution cookies.
|
|
134
135
|
|
|
136
|
+
Auth failures emit the operational event `auth_failed` with
|
|
137
|
+
`provider`, `reason`, and `auth_path`. Failure payloads never include raw
|
|
138
|
+
email addresses, passwords, magic codes, or reset tokens.
|
|
139
|
+
|
|
135
140
|
## Using `context.locals.user`
|
|
136
141
|
|
|
137
142
|
```typescript
|
|
@@ -184,9 +189,9 @@ internal package-test helper and is not exported from
|
|
|
184
189
|
## OAuth Flow Summary
|
|
185
190
|
|
|
186
191
|
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
|
|
187
|
-
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
|
|
188
|
-
3. For consumer-hosted password or magic-code flows, package-owned POST routes call `{issuerInternal ?? issuer}/api/v1/auth
|
|
189
|
-
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
|
|
192
|
+
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 `{cookiePrefix}_pkce` at both `/` and `callbackPath`: the root-path copy is visible to package-owned POST handlers and JSON verify flows, while the callback-path copy preserves native form handoff through the callback route. The forgot-password route seeds local flow cookies but does not set a provider cookie.
|
|
193
|
+
3. For consumer-hosted password or magic-code flows, package-owned POST routes call `{issuerInternal ?? issuer}/api/v1/auth/*`; native forms receive `303` redirects through `/api/auth/callback?code=...&state=...`, while `Accept: application/json` verify callers exchange the code in-place, set session cookies, emit the success event, and receive `{ ok, redirect }` for the final destination.
|
|
194
|
+
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, emits success/failure analytics, and redirects to the original page or returns a JSON redirect contract for `Accept: application/json`.
|
|
190
195
|
|
|
191
196
|
## Signed Identity Cookie
|
|
192
197
|
|
|
@@ -196,7 +201,7 @@ internal package-test helper and is not exported from
|
|
|
196
201
|
- Payload: lowercase hex `sha256(sub + GL_IDENTITY_SECRET)`, where `sub` is the access-token subject. The raw subject is never written into the cookie.
|
|
197
202
|
- Secret: `GL_IDENTITY_SECRET`, a per-tenant 32-byte standard-base64 value
|
|
198
203
|
- Attributes: `HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000`
|
|
199
|
-
- Issued: callback success after token exchange
|
|
204
|
+
- Issued: callback success or JSON verify success after token exchange
|
|
200
205
|
- Cleared: `/logout` with `Max-Age=0`
|
|
201
206
|
|
|
202
207
|
Verification helper:
|
|
@@ -214,19 +219,19 @@ if (result.valid) {
|
|
|
214
219
|
|
|
215
220
|
## Analytics Integration
|
|
216
221
|
|
|
217
|
-
Optional analytics helpers exist for `login_completed
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`payload.isNewUser` for legacy issuers.
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
Optional analytics helpers exist for `login_completed`,
|
|
223
|
+
`signup_completed`, and `auth_failed`. The success event name is derived
|
|
224
|
+
from the access-token JWT `isNewUser` claim, preferring
|
|
225
|
+
`payload.properties.isNewUser` and falling back to top-level
|
|
226
|
+
`payload.isNewUser` for legacy issuers. The package uses the public
|
|
227
|
+
`@growth-labs/analytics/utils.trackServerEvent` contract when the optional
|
|
228
|
+
analytics peer is installed.
|
|
224
229
|
|
|
225
230
|
## Key Patterns
|
|
226
231
|
|
|
227
232
|
- Virtual module: `virtual:growth-labs/auth/config` (slash, not hyphen)
|
|
228
233
|
- Cookie names: `{cookiePrefix}_at`, `{cookiePrefix}_rt`, `{cookiePrefix}_pkce`, `{cookiePrefix}_state`, `{cookiePrefix}_redirect`, `{cookiePrefix}_provider`, `gl_id`
|
|
229
|
-
- PKCE flow with `code_verifier` / `code_challenge` (S256); `{cookiePrefix}_pkce` is scoped to `callbackPath` for issuer redirect flows and
|
|
234
|
+
- PKCE flow with `code_verifier` / `code_challenge` (S256); `{cookiePrefix}_pkce` is scoped to `callbackPath` for issuer redirect flows and set at both `/` and `callbackPath` for consumer-hosted password/code flows that can either post JSON in-place or hand off to callback
|
|
230
235
|
- `issuer` is browser-facing; optional `issuerInternal` is used for token exchange, silent refresh, JWT verification/JWKS lookup, and revocation. If omitted, `issuerInternal` falls back to `issuer`.
|
|
231
236
|
- Confidential OAuth clients use `client_secret_basic` on token exchange and refresh when `clientSecret` is configured. Prefer a Worker secret binding name such as `AUTH_CLIENT_SECRET`; runtime code resolves that binding before calling the issuer.
|
|
232
237
|
- RFC 8707 `resource` is sent on authorize, token exchange, and refresh when configured
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-request.d.ts","sourceRoot":"","sources":["../../../src/routes/api/code-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"code-request.d.ts","sourceRoot":"","sources":["../../../src/routes/api/code-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuBnE"}
|
|
@@ -11,6 +11,6 @@ export async function POST(context) {
|
|
|
11
11
|
const email = await readFormString(form, 'email');
|
|
12
12
|
const redirect = form.get('redirect')?.toString();
|
|
13
13
|
const result = await authClient(context).code.request({ email });
|
|
14
|
-
return statusRedirect(context, result, options.verifyCodePath, { email, sent: '1', redirect }, options.verifyCodePath, { email, redirect });
|
|
14
|
+
return statusRedirect(context, result, options.verifyCodePath, { email, sent: '1', redirect }, options.verifyCodePath, { email, redirect }, 'email-code');
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=code-request.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-request.js","sourceRoot":"","sources":["../../../src/routes/api/code-request.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAA;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAEhE,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,cAAc,EACtB,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAC9B,OAAO,CAAC,cAAc,EACtB,EAAE,KAAK,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"code-request.js","sourceRoot":"","sources":["../../../src/routes/api/code-request.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAA;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAEhE,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,cAAc,EACtB,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAC9B,OAAO,CAAC,cAAc,EACtB,EAAE,KAAK,EAAE,QAAQ,EAAE,EACnB,YAAY,CACZ,CAAA;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset-confirm.d.ts","sourceRoot":"","sources":["../../../src/routes/api/password-reset-confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"password-reset-confirm.d.ts","sourceRoot":"","sources":["../../../src/routes/api/password-reset-confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0BnE"}
|
|
@@ -14,6 +14,6 @@ export async function POST(context) {
|
|
|
14
14
|
token,
|
|
15
15
|
newPassword: password,
|
|
16
16
|
});
|
|
17
|
-
return statusRedirect(context, result, options.loginPath, { reset: 'success' }, options.resetPasswordPath, { token });
|
|
17
|
+
return statusRedirect(context, result, options.loginPath, { reset: 'success' }, options.resetPasswordPath, { token }, 'password');
|
|
18
18
|
}
|
|
19
19
|
//# sourceMappingURL=password-reset-confirm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset-confirm.js","sourceRoot":"","sources":["../../../src/routes/api/password-reset-confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;QAC/D,KAAK;QACL,WAAW,EAAE,QAAQ;KACrB,CAAC,CAAA;IAEF,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,SAAS,EACjB,EAAE,KAAK,EAAE,SAAS,EAAE,EACpB,OAAO,CAAC,iBAAiB,EACzB,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"password-reset-confirm.js","sourceRoot":"","sources":["../../../src/routes/api/password-reset-confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;QAC/D,KAAK;QACL,WAAW,EAAE,QAAQ;KACrB,CAAC,CAAA;IAEF,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,SAAS,EACjB,EAAE,KAAK,EAAE,SAAS,EAAE,EACpB,OAAO,CAAC,iBAAiB,EACzB,EAAE,KAAK,EAAE,EACT,UAAU,CACV,CAAA;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset-request.d.ts","sourceRoot":"","sources":["../../../src/routes/api/password-reset-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"password-reset-request.d.ts","sourceRoot":"","sources":["../../../src/routes/api/password-reset-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,YAAY,EAKjB,MAAM,aAAa,CAAA;AAEpB,wBAAsB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBnE"}
|
|
@@ -10,6 +10,6 @@ export async function POST(context) {
|
|
|
10
10
|
const form = await readForm(context);
|
|
11
11
|
const email = await readFormString(form, 'email');
|
|
12
12
|
const result = await authClient(context).password.reset.request({ email });
|
|
13
|
-
return statusRedirect(context, result, options.forgotPasswordPath, { sent: '1' }, options.forgotPasswordPath, { email });
|
|
13
|
+
return statusRedirect(context, result, options.forgotPasswordPath, { sent: '1' }, options.forgotPasswordPath, { email }, 'password');
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=password-reset-request.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset-request.js","sourceRoot":"","sources":["../../../src/routes/api/password-reset-request.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAE1E,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,kBAAkB,EAC1B,EAAE,IAAI,EAAE,GAAG,EAAE,EACb,OAAO,CAAC,kBAAkB,EAC1B,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"password-reset-request.js","sourceRoot":"","sources":["../../../src/routes/api/password-reset-request.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EAET,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,cAAc,GACd,MAAM,aAAa,CAAA;AAEpB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAqB;IAC/C,MAAM,aAAa,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IAEvC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAE1E,OAAO,cAAc,CACpB,OAAO,EACP,MAAM,EACN,OAAO,CAAC,kBAAkB,EAC1B,EAAE,IAAI,EAAE,GAAG,EAAE,EACb,OAAO,CAAC,kBAAkB,EAC1B,EAAE,KAAK,EAAE,EACT,UAAU,CACV,CAAA;AACF,CAAC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getConfig } from '../../config-store.js';
|
|
2
2
|
import { type AuthClientCodeResult, type AuthClientOkResult, authClient } from '../../server.js';
|
|
3
|
+
import { type AuthAnalyticsProvider } from '../../utils/analytics.js';
|
|
3
4
|
import { type RouteContext, readForm, redirectTo } from '../flow.js';
|
|
4
5
|
export declare function readFormString(form: FormData, key: string, options?: {
|
|
5
6
|
trim?: boolean;
|
|
6
7
|
}): Promise<string>;
|
|
7
8
|
export declare function rejectCrossSitePost(context: RouteContext): Response | null;
|
|
8
|
-
export declare function callbackRedirect(context: RouteContext, result: AuthClientCodeResult, errorPath: string, errorParams: Record<string, string | undefined>, provider?: 'password' | 'email-code'): Response;
|
|
9
|
-
export declare function statusRedirect(context: RouteContext, result: AuthClientOkResult, successPath: string, successParams: Record<string, string | undefined>, errorPath: string, errorParams: Record<string, string | undefined
|
|
9
|
+
export declare function callbackRedirect(context: RouteContext, result: AuthClientCodeResult, errorPath: string, errorParams: Record<string, string | undefined>, provider?: 'password' | 'email-code'): Promise<Response> | Response;
|
|
10
|
+
export declare function statusRedirect(context: RouteContext, result: AuthClientOkResult, successPath: string, successParams: Record<string, string | undefined>, errorPath: string, errorParams: Record<string, string | undefined>, provider?: AuthAnalyticsProvider): Response;
|
|
10
11
|
export type { RouteContext };
|
|
11
12
|
export { authClient, getConfig, readForm, redirectTo };
|
|
12
13
|
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EACN,KAAK,qBAAqB,EAI1B,MAAM,0BAA0B,CAAA;AAWjC,OAAO,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAoB,UAAU,EAAE,MAAM,YAAY,CAAA;AAItF,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,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,GAAG,IAAI,CAe1E;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,EAC/C,QAAQ,CAAC,EAAE,UAAU,GAAG,YAAY,GAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAsC9B;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,EAC/C,QAAQ,CAAC,EAAE,qBAAqB,GAC9B,QAAQ,CAMV;AAyBD,YAAY,EAAE,YAAY,EAAE,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { getConfig } from '../../config-store.js';
|
|
2
|
+
import { getRuntimeEnv } from '../../runtime.js';
|
|
2
3
|
import { authClient } from '../../server.js';
|
|
3
|
-
import {
|
|
4
|
+
import { analyticsAvailable, emitAuthFailureEvent, emitAuthSuccessEvent, } from '../../utils/analytics.js';
|
|
5
|
+
import { clearAuthFlowCookies, getCookie, setCookie, setIdentityCookie, setTokenCookies, } from '../../utils/cookies.js';
|
|
6
|
+
import { createIdentityCookiePayload, signIdentityCookie } from '../../utils/identity-cookie.js';
|
|
7
|
+
import { exchangeCode } from '../../utils/openauth.js';
|
|
8
|
+
import { resolveWaitUntil } from '../../utils/wait-until.js';
|
|
4
9
|
import { readForm, redirectLocation, redirectTo } from '../flow.js';
|
|
5
10
|
const CROSS_SITE_POST_MESSAGE = 'Cross-site POST form submissions are forbidden';
|
|
6
11
|
export async function readFormString(form, key, options = {}) {
|
|
@@ -24,13 +29,18 @@ export function rejectCrossSitePost(context) {
|
|
|
24
29
|
}
|
|
25
30
|
export function callbackRedirect(context, result, errorPath, errorParams, provider) {
|
|
26
31
|
if ('error' in result) {
|
|
32
|
+
queueAuthFailure(context, provider, result.error);
|
|
27
33
|
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error);
|
|
28
34
|
}
|
|
29
35
|
const options = getConfig();
|
|
30
36
|
const state = getCookie(context.request, `${options.cookiePrefix}_state`);
|
|
31
37
|
if (!state) {
|
|
38
|
+
queueAuthFailure(context, provider, 'missing_auth_flow');
|
|
32
39
|
return routeResult(context, errorPath, { ...errorParams, error: 'missing_auth_flow' }, 'missing_auth_flow');
|
|
33
40
|
}
|
|
41
|
+
if (wantsJson(context)) {
|
|
42
|
+
return completeJsonAuth(context, result.code, errorPath, errorParams, provider);
|
|
43
|
+
}
|
|
34
44
|
const response = mutableHeaders(routeResult(context, options.callbackPath, {
|
|
35
45
|
code: result.code,
|
|
36
46
|
state,
|
|
@@ -41,8 +51,9 @@ export function callbackRedirect(context, result, errorPath, errorParams, provid
|
|
|
41
51
|
}
|
|
42
52
|
return response;
|
|
43
53
|
}
|
|
44
|
-
export function statusRedirect(context, result, successPath, successParams, errorPath, errorParams) {
|
|
54
|
+
export function statusRedirect(context, result, successPath, successParams, errorPath, errorParams, provider) {
|
|
45
55
|
if ('error' in result) {
|
|
56
|
+
queueAuthFailure(context, provider, result.error);
|
|
46
57
|
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error);
|
|
47
58
|
}
|
|
48
59
|
return routeResult(context, successPath, successParams);
|
|
@@ -70,6 +81,54 @@ function mutableHeaders(response) {
|
|
|
70
81
|
headers: new Headers(response.headers),
|
|
71
82
|
});
|
|
72
83
|
}
|
|
84
|
+
async function completeJsonAuth(context, code, errorPath, errorParams, provider) {
|
|
85
|
+
const options = getConfig();
|
|
86
|
+
const codeVerifier = getCookie(context.request, `${options.cookiePrefix}_pkce`);
|
|
87
|
+
if (!codeVerifier) {
|
|
88
|
+
queueAuthFailure(context, provider, 'missing_auth_flow');
|
|
89
|
+
return routeResult(context, errorPath, { ...errorParams, error: 'missing_auth_flow' }, 'missing_auth_flow');
|
|
90
|
+
}
|
|
91
|
+
const tokenResponse = await exchangeCode(context.url, code, codeVerifier, provider);
|
|
92
|
+
if (!tokenResponse) {
|
|
93
|
+
queueAuthFailure(context, provider, 'token_exchange_failed');
|
|
94
|
+
return routeResult(context, errorPath, { ...errorParams, error: 'auth_failed' }, 'auth_failed');
|
|
95
|
+
}
|
|
96
|
+
const redirectPath = getCookie(context.request, `${options.cookiePrefix}_redirect`) || '/';
|
|
97
|
+
const redirect = new URL(redirectPath, context.url.origin).toString();
|
|
98
|
+
const response = mutableHeaders(Response.json({ ok: true, redirect }));
|
|
99
|
+
setTokenCookies(response, options, tokenResponse.accessToken, tokenResponse.refreshToken);
|
|
100
|
+
clearAuthFlowCookies(response, options);
|
|
101
|
+
await setOptionalIdentityCookie(response, tokenResponse.claims.sub);
|
|
102
|
+
if (analyticsAvailable(context)) {
|
|
103
|
+
resolveWaitUntil(context)(emitAuthSuccessEvent(context, {
|
|
104
|
+
provider: tokenResponse.provider,
|
|
105
|
+
isNewUser: tokenResponse.isNewUser,
|
|
106
|
+
authPath: context.url.pathname,
|
|
107
|
+
identityUserId: tokenResponse.claims.sub,
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
return response;
|
|
111
|
+
}
|
|
112
|
+
function queueAuthFailure(context, provider, reason) {
|
|
113
|
+
if (!analyticsAvailable(context))
|
|
114
|
+
return;
|
|
115
|
+
resolveWaitUntil(context)(emitAuthFailureEvent(context, {
|
|
116
|
+
provider,
|
|
117
|
+
reason,
|
|
118
|
+
authPath: context.url.pathname,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async function setOptionalIdentityCookie(response, subject) {
|
|
122
|
+
const secret = getRuntimeEnv().GL_IDENTITY_SECRET;
|
|
123
|
+
if (typeof secret !== 'string' || !secret)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
setIdentityCookie(response, await signIdentityCookie(await createIdentityCookiePayload(subject, secret), secret));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Keep login working if the optional analytics identity secret is misconfigured.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
73
132
|
function authFlowCookieAttrs() {
|
|
74
133
|
return {
|
|
75
134
|
path: '/',
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/routes/api/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"AAkBA,UAAU,eAAe;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAKhC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAC/C;AAoCD,wBAAsB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0GrE"}
|
package/dist/routes/callback.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getConfig } from '../config-store.js';
|
|
2
2
|
import { getRuntimeEnv } from '../runtime.js';
|
|
3
|
-
import { analyticsAvailable,
|
|
3
|
+
import { analyticsAvailable, emitAuthFailureEvent, emitAuthSuccessEvent, } from '../utils/analytics.js';
|
|
4
4
|
import { clearAuthFlowCookies, getCookie, setIdentityCookie, setTokenCookies, } from '../utils/cookies.js';
|
|
5
5
|
import { createIdentityCookiePayload, signIdentityCookie } from '../utils/identity-cookie.js';
|
|
6
6
|
import { exchangeCode } from '../utils/openauth.js';
|
|
@@ -10,33 +10,71 @@ function getIdentitySecret() {
|
|
|
10
10
|
const secret = getRuntimeEnv().GL_IDENTITY_SECRET;
|
|
11
11
|
return typeof secret === 'string' && secret.length > 0 ? secret : null;
|
|
12
12
|
}
|
|
13
|
+
function wantsJson(request) {
|
|
14
|
+
const accept = request.headers.get('accept') ?? '';
|
|
15
|
+
return accept
|
|
16
|
+
.split(',')
|
|
17
|
+
.some((value) => value.trim().toLowerCase().split(';')[0] === 'application/json');
|
|
18
|
+
}
|
|
19
|
+
function loginErrorResponse(request, origin, loginPath) {
|
|
20
|
+
const redirect = new URL(`${loginPath}?error=auth_failed`, origin).toString();
|
|
21
|
+
if (wantsJson(request)) {
|
|
22
|
+
return Response.json({ error: 'auth_failed', redirect }, { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
return Response.redirect(redirect, 302);
|
|
25
|
+
}
|
|
26
|
+
function queueAuthFailure(context, provider, reason) {
|
|
27
|
+
if (!analyticsAvailable(context))
|
|
28
|
+
return;
|
|
29
|
+
const promise = emitAuthFailureEvent(context, {
|
|
30
|
+
provider,
|
|
31
|
+
reason,
|
|
32
|
+
authPath: context.url.pathname,
|
|
33
|
+
});
|
|
34
|
+
resolveWaitUntil(context)(promise);
|
|
35
|
+
}
|
|
13
36
|
export async function GET(context) {
|
|
14
37
|
const options = getConfig();
|
|
15
38
|
const { url, request } = context;
|
|
16
39
|
const code = url.searchParams.get('code');
|
|
17
40
|
const state = url.searchParams.get('state');
|
|
41
|
+
const error = url.searchParams.get('error');
|
|
18
42
|
// Verify CSRF state
|
|
19
43
|
const storedState = getCookie(request, `${options.cookiePrefix}_state`);
|
|
20
44
|
if (!state || !storedState || !verifyState(state, storedState)) {
|
|
45
|
+
queueAuthFailure(context, getCookie(request, `${options.cookiePrefix}_provider`), 'missing_auth_flow');
|
|
21
46
|
return new Response('Invalid state parameter', { status: 403 });
|
|
22
47
|
}
|
|
23
48
|
// Read PKCE verifier
|
|
24
49
|
const codeVerifier = getCookie(request, `${options.cookiePrefix}_pkce`);
|
|
25
50
|
if (!codeVerifier) {
|
|
51
|
+
queueAuthFailure(context, getCookie(request, `${options.cookiePrefix}_provider`), 'missing_auth_flow');
|
|
26
52
|
return new Response('Missing PKCE verifier', { status: 400 });
|
|
27
53
|
}
|
|
28
54
|
const provider = getCookie(request, `${options.cookiePrefix}_provider`);
|
|
29
55
|
const authPath = getCookie(request, `${options.cookiePrefix}_auth_path`) || url.pathname;
|
|
56
|
+
if (error) {
|
|
57
|
+
queueAuthFailure(context, provider, error);
|
|
58
|
+
return loginErrorResponse(request, url.origin, options.loginPath);
|
|
59
|
+
}
|
|
60
|
+
if (!code) {
|
|
61
|
+
queueAuthFailure(context, provider, 'invalid_request');
|
|
62
|
+
return loginErrorResponse(request, url.origin, options.loginPath);
|
|
63
|
+
}
|
|
30
64
|
// Exchange code for tokens
|
|
31
65
|
const tokenResponse = await exchangeCode(url, code, codeVerifier, provider === 'google' || provider === 'password' || provider === 'email-code'
|
|
32
66
|
? provider
|
|
33
67
|
: undefined);
|
|
34
68
|
if (!tokenResponse) {
|
|
35
|
-
|
|
69
|
+
queueAuthFailure(context, provider, 'token_exchange_failed');
|
|
70
|
+
return loginErrorResponse(request, url.origin, options.loginPath);
|
|
36
71
|
}
|
|
37
72
|
// Build redirect response
|
|
38
73
|
const redirectPath = getCookie(request, `${options.cookiePrefix}_redirect`) || '/';
|
|
39
|
-
const
|
|
74
|
+
const redirect = new URL(redirectPath, url.origin).toString();
|
|
75
|
+
const response = wantsJson(request)
|
|
76
|
+
? Response.json({ ok: true, redirect })
|
|
77
|
+
: Response.redirect(redirect, 302);
|
|
40
78
|
// Mutable copy for setting cookies
|
|
41
79
|
const mutableResponse = new Response(response.body, {
|
|
42
80
|
status: response.status,
|
|
@@ -62,12 +100,11 @@ export async function GET(context) {
|
|
|
62
100
|
// the root cause of the missing `login_completed` / `signup_completed`
|
|
63
101
|
// rows in `gl_conversion_events` after the Fronts cutover.
|
|
64
102
|
if (analyticsAvailable(context)) {
|
|
65
|
-
const
|
|
66
|
-
const promise = emitAuthEvent(context, eventName, {
|
|
103
|
+
const promise = emitAuthSuccessEvent(context, {
|
|
67
104
|
provider: tokenResponse.provider,
|
|
68
105
|
isNewUser: tokenResponse.isNewUser,
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
authPath,
|
|
107
|
+
identityUserId: tokenResponse.claims.sub,
|
|
71
108
|
});
|
|
72
109
|
resolveWaitUntil(context)(promise);
|
|
73
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/routes/callback.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/flow.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { ResolvedAuthOptions } from '../options.js';
|
|
|
2
2
|
export interface RouteContext {
|
|
3
3
|
request: Request;
|
|
4
4
|
url: URL;
|
|
5
|
+
locals?: {
|
|
6
|
+
cfContext?: {
|
|
7
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
5
11
|
}
|
|
6
12
|
export declare function withAuthFlowCookies(context: RouteContext, options: ResolvedAuthOptions, response: Response, redirectPath: string, provider?: string): Promise<Response>;
|
|
7
13
|
export declare function redirectLocation(context: RouteContext, pathname: string, params: Record<string, string | undefined>): string;
|
|
@@ -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;
|
|
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;IACR,MAAM,CAAC,EAAE;QACR,SAAS,CAAC,EAAE;YACX,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;SAC/C,CAAA;KACD,CAAA;IACD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAC/C;AAED,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,CAAC,CAYnB;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.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;
|
|
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;AAarD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAAqB,EACrB,OAA4B,EAC5B,QAAkB,EAClB,YAAoB,EACpB,QAAiB;IAEjB,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;QACb,mBAAmB,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;KAC3C,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"}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
export type AuthAnalyticsProvider = 'google' | 'email-code' | 'password' | 'unknown';
|
|
2
|
+
export interface AuthSuccessEventInput {
|
|
3
|
+
provider: AuthAnalyticsProvider | string | null | undefined;
|
|
4
|
+
isNewUser: boolean;
|
|
5
|
+
authPath: string;
|
|
6
|
+
identityUserId: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthFailureEventInput {
|
|
9
|
+
provider: AuthAnalyticsProvider | string | null | undefined;
|
|
10
|
+
reason: string | null | undefined;
|
|
11
|
+
authPath: string;
|
|
12
|
+
}
|
|
1
13
|
export declare function emitAuthEvent(context: unknown, eventName: string, data: Record<string, unknown>): Promise<void>;
|
|
14
|
+
export declare function normalizeAuthProvider(provider: AuthAnalyticsProvider | string | null | undefined): AuthAnalyticsProvider;
|
|
15
|
+
export declare function normalizeAuthFailureReason(reason: string | null | undefined): string;
|
|
16
|
+
export declare function authSuccessEventName(isNewUser: boolean): 'signup_completed' | 'login_completed';
|
|
17
|
+
export declare function emitAuthSuccessEvent(context: unknown, input: AuthSuccessEventInput): Promise<void>;
|
|
18
|
+
export declare function emitAuthFailureEvent(context: unknown, input: AuthFailureEventInput): Promise<void>;
|
|
2
19
|
export declare function analyticsAvailable(context: {
|
|
3
20
|
locals?: Record<string, unknown>;
|
|
4
21
|
}): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAA;AAEpF,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC3D,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC3D,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,QAAQ,EAAE,MAAM,CAAA;CAChB;AAiBD,wBAAsB,aAAa,CAClC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACzD,qBAAqB,CAEvB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGpF;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,GAAG,kBAAkB,GAAG,iBAAiB,CAE/F;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,OAAO,CAGzF;AAED,wBAAsB,cAAc,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf;AAKD,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
|
package/dist/utils/analytics.js
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const mod = await importOptionalModule(ANALYTICS_UTILS_MODULE);
|
|
17
|
-
let candidate;
|
|
18
|
-
try {
|
|
19
|
-
candidate = mod?.trackServerEvent;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
candidate = undefined;
|
|
23
|
-
}
|
|
24
|
-
if (typeof candidate === 'function') {
|
|
25
|
-
trackFn = candidate;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
import { trackServerEvent } from '@growth-labs/analytics/utils';
|
|
2
|
+
const AUTH_PROVIDERS = new Set(['google', 'email-code', 'password']);
|
|
3
|
+
const AUTH_FAILURE_REASONS = new Set([
|
|
4
|
+
'auth_failed',
|
|
5
|
+
'invalid_credentials',
|
|
6
|
+
'invalid_code',
|
|
7
|
+
'invalid_request',
|
|
8
|
+
'invalid_response',
|
|
9
|
+
'invalid_token',
|
|
10
|
+
'missing_auth_flow',
|
|
11
|
+
'oauth_denied',
|
|
12
|
+
'request_failed',
|
|
13
|
+
'send_failed',
|
|
14
|
+
'token_exchange_failed',
|
|
15
|
+
]);
|
|
28
16
|
export async function emitAuthEvent(context, eventName, data) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
// Bind via static import so wrangler/esbuild bundles the target at build
|
|
18
|
+
// time. The previous variable-path dynamic import (await import(modulePath))
|
|
19
|
+
// could not be statically analyzed; in Cloudflare Workers the bundle did
|
|
20
|
+
// not include @growth-labs/analytics/utils, the runtime resolved null, and
|
|
21
|
+
// every auth event silently no-op'd — surfaced post-2026-05-22 as
|
|
22
|
+
// `signup_completed` / `login_completed` rows never landing in
|
|
23
|
+
// gl_conversion_events on the new Fronts Worker. See peerDependency.
|
|
24
|
+
await trackServerEvent(context, eventName, data);
|
|
25
|
+
}
|
|
26
|
+
export function normalizeAuthProvider(provider) {
|
|
27
|
+
return provider && AUTH_PROVIDERS.has(provider) ? provider : 'unknown';
|
|
28
|
+
}
|
|
29
|
+
export function normalizeAuthFailureReason(reason) {
|
|
30
|
+
if (reason === 'access_denied')
|
|
31
|
+
return 'oauth_denied';
|
|
32
|
+
return reason && AUTH_FAILURE_REASONS.has(reason) ? reason : 'auth_failed';
|
|
33
|
+
}
|
|
34
|
+
export function authSuccessEventName(isNewUser) {
|
|
35
|
+
return isNewUser ? 'signup_completed' : 'login_completed';
|
|
36
|
+
}
|
|
37
|
+
export async function emitAuthSuccessEvent(context, input) {
|
|
38
|
+
return emitAuthEvent(context, authSuccessEventName(input.isNewUser), {
|
|
39
|
+
provider: normalizeAuthProvider(input.provider),
|
|
40
|
+
isNewUser: input.isNewUser,
|
|
41
|
+
auth_path: input.authPath,
|
|
42
|
+
identity_user_id: input.identityUserId,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function emitAuthFailureEvent(context, input) {
|
|
46
|
+
return emitAuthEvent(context, 'auth_failed', {
|
|
47
|
+
provider: normalizeAuthProvider(input.provider),
|
|
48
|
+
reason: normalizeAuthFailureReason(input.reason),
|
|
49
|
+
auth_path: input.authPath,
|
|
50
|
+
});
|
|
33
51
|
}
|
|
34
52
|
export function analyticsAvailable(context) {
|
|
35
53
|
void context;
|
|
@@ -38,8 +56,10 @@ export function analyticsAvailable(context) {
|
|
|
38
56
|
export async function trackAuthEvent(context, eventName, data = {}) {
|
|
39
57
|
return emitAuthEvent(context, eventName, data);
|
|
40
58
|
}
|
|
59
|
+
// Test-only export retained for compatibility with existing tests that reset
|
|
60
|
+
// internal state between runs. Now a no-op since there's no internal state
|
|
61
|
+
// to reset — trackFn is the bound static import.
|
|
41
62
|
export function _resetAnalytics() {
|
|
42
|
-
|
|
43
|
-
resolved = false;
|
|
63
|
+
// noop
|
|
44
64
|
}
|
|
45
65
|
//# sourceMappingURL=analytics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAiB/D,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAAA;AACpE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACpC,aAAa;IACb,qBAAqB;IACrB,cAAc;IACd,iBAAiB;IACjB,kBAAkB;IAClB,eAAe;IACf,mBAAmB;IACnB,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,uBAAuB;CACvB,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,OAAgB,EAChB,SAAiB,EACjB,IAA6B;IAE7B,yEAAyE;IACzE,6EAA6E;IAC7E,yEAAyE;IACzE,2EAA2E;IAC3E,kEAAkE;IAClE,+DAA+D;IAC/D,qEAAqE;IACrE,MAAM,gBAAgB,CAAC,OAAiD,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;AAC3F,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,QAA2D;IAE3D,OAAO,QAAQ,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,QAAkC,CAAC,CAAC,CAAC,SAAS,CAAA;AAClG,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAiC;IAC3E,IAAI,MAAM,KAAK,eAAe;QAAE,OAAO,cAAc,CAAA;IACrD,OAAO,MAAM,IAAI,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAA;AAC3E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAkB;IACtD,OAAO,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,iBAAiB,CAAA;AAC1D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,OAAgB,EAChB,KAA4B;IAE5B,OAAO,aAAa,CAAC,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;QACpE,QAAQ,EAAE,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC/C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,QAAQ;QACzB,gBAAgB,EAAE,KAAK,CAAC,cAAc;KACtC,CAAC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,OAAgB,EAChB,KAA4B;IAE5B,OAAO,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE;QAC5C,QAAQ,EAAE,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC/C,MAAM,EAAE,0BAA0B,CAAC,KAAK,CAAC,MAAM,CAAC;QAChD,SAAS,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAA6C;IAC/E,KAAK,OAAO,CAAA;IACZ,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,OAAgB,EAChB,SAAiB,EACjB,OAAgC,EAAE;IAElC,OAAO,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;AAC/C,CAAC;AAED,6EAA6E;AAC7E,2EAA2E;AAC3E,iDAAiD;AACjD,MAAM,UAAU,eAAe;IAC9B,OAAO;AACR,CAAC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { emitAuthEvent, trackAuthEvent } from './analytics.js';
|
|
1
|
+
export { type AuthAnalyticsProvider, type AuthFailureEventInput, type AuthSuccessEventInput, authSuccessEventName, emitAuthEvent, emitAuthFailureEvent, emitAuthSuccessEvent, normalizeAuthFailureReason, normalizeAuthProvider, trackAuthEvent, } from './analytics.js';
|
|
2
2
|
export { type BridgeMigrationOptions, type BridgeMigrationStatement, buildLegacyIdentitySubjectCanary, buildLegacyIdentitySubjectMigration, extractIdentityId, findLegacyIdentitySubjects, IDENTITY_SUB_FORMAT_DRIFT, IDENTITY_SUBJECT_PREFIX, IdentitySubFormatDriftError, normalizeIdentitySubject, rewriteLegacyIdentitySubjects, } from './identity.js';
|
|
3
3
|
export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
|
|
4
4
|
export { getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl } from './openauth.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,mCAAmC,EACnC,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { emitAuthEvent, trackAuthEvent } from './analytics.js';
|
|
1
|
+
export { authSuccessEventName, emitAuthEvent, emitAuthFailureEvent, emitAuthSuccessEvent, normalizeAuthFailureReason, normalizeAuthProvider, trackAuthEvent, } from './analytics.js';
|
|
2
2
|
export { buildLegacyIdentitySubjectCanary, buildLegacyIdentitySubjectMigration, extractIdentityId, findLegacyIdentitySubjects, IDENTITY_SUB_FORMAT_DRIFT, IDENTITY_SUBJECT_PREFIX, IdentitySubFormatDriftError, normalizeIdentitySubject, rewriteLegacyIdentitySubjects, } from './identity.js';
|
|
3
3
|
export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
|
|
4
4
|
export { getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl } from './openauth.js';
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAGN,gCAAgC,EAChC,mCAAmC,EACnC,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growth-labs/auth",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -46,12 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"astro": "^6.1.10",
|
|
49
|
-
"@growth-labs/analytics": ">=0.
|
|
50
|
-
},
|
|
51
|
-
"peerDependenciesMeta": {
|
|
52
|
-
"@growth-labs/analytics": {
|
|
53
|
-
"optional": true
|
|
54
|
-
}
|
|
49
|
+
"@growth-labs/analytics": ">=0.5.1"
|
|
55
50
|
},
|
|
56
51
|
"dependencies": {
|
|
57
52
|
"zod": "^3.23.0",
|
|
@@ -62,7 +57,7 @@
|
|
|
62
57
|
"typescript": "^5.8.0",
|
|
63
58
|
"vite": "^7.3.2",
|
|
64
59
|
"vitest": "^3.0.0",
|
|
65
|
-
"@growth-labs/analytics": "0.5.
|
|
60
|
+
"@growth-labs/analytics": "0.5.1"
|
|
66
61
|
},
|
|
67
62
|
"scripts": {
|
|
68
63
|
"build": "tsc",
|
package/src/routes/api/shared.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { getConfig } from '../../config-store.js'
|
|
2
|
+
import { getRuntimeEnv } from '../../runtime.js'
|
|
2
3
|
import { type AuthClientCodeResult, type AuthClientOkResult, authClient } from '../../server.js'
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type AuthAnalyticsProvider,
|
|
6
|
+
analyticsAvailable,
|
|
7
|
+
emitAuthFailureEvent,
|
|
8
|
+
emitAuthSuccessEvent,
|
|
9
|
+
} from '../../utils/analytics.js'
|
|
10
|
+
import {
|
|
11
|
+
clearAuthFlowCookies,
|
|
12
|
+
getCookie,
|
|
13
|
+
setCookie,
|
|
14
|
+
setIdentityCookie,
|
|
15
|
+
setTokenCookies,
|
|
16
|
+
} from '../../utils/cookies.js'
|
|
17
|
+
import { createIdentityCookiePayload, signIdentityCookie } from '../../utils/identity-cookie.js'
|
|
18
|
+
import { exchangeCode } from '../../utils/openauth.js'
|
|
19
|
+
import { resolveWaitUntil } from '../../utils/wait-until.js'
|
|
4
20
|
import { type RouteContext, readForm, redirectLocation, redirectTo } from '../flow.js'
|
|
5
21
|
|
|
6
22
|
const CROSS_SITE_POST_MESSAGE = 'Cross-site POST form submissions are forbidden'
|
|
@@ -37,14 +53,16 @@ export function callbackRedirect(
|
|
|
37
53
|
errorPath: string,
|
|
38
54
|
errorParams: Record<string, string | undefined>,
|
|
39
55
|
provider?: 'password' | 'email-code',
|
|
40
|
-
): Response {
|
|
56
|
+
): Promise<Response> | Response {
|
|
41
57
|
if ('error' in result) {
|
|
58
|
+
queueAuthFailure(context, provider, result.error)
|
|
42
59
|
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error)
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
const options = getConfig()
|
|
46
63
|
const state = getCookie(context.request, `${options.cookiePrefix}_state`)
|
|
47
64
|
if (!state) {
|
|
65
|
+
queueAuthFailure(context, provider, 'missing_auth_flow')
|
|
48
66
|
return routeResult(
|
|
49
67
|
context,
|
|
50
68
|
errorPath,
|
|
@@ -53,6 +71,10 @@ export function callbackRedirect(
|
|
|
53
71
|
)
|
|
54
72
|
}
|
|
55
73
|
|
|
74
|
+
if (wantsJson(context)) {
|
|
75
|
+
return completeJsonAuth(context, result.code, errorPath, errorParams, provider)
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
const response = mutableHeaders(
|
|
57
79
|
routeResult(context, options.callbackPath, {
|
|
58
80
|
code: result.code,
|
|
@@ -78,8 +100,10 @@ export function statusRedirect(
|
|
|
78
100
|
successParams: Record<string, string | undefined>,
|
|
79
101
|
errorPath: string,
|
|
80
102
|
errorParams: Record<string, string | undefined>,
|
|
103
|
+
provider?: AuthAnalyticsProvider,
|
|
81
104
|
): Response {
|
|
82
105
|
if ('error' in result) {
|
|
106
|
+
queueAuthFailure(context, provider, result.error)
|
|
83
107
|
return routeResult(context, errorPath, { ...errorParams, error: result.error }, result.error)
|
|
84
108
|
}
|
|
85
109
|
return routeResult(context, successPath, successParams)
|
|
@@ -119,6 +143,80 @@ function mutableHeaders(response: Response): Response {
|
|
|
119
143
|
})
|
|
120
144
|
}
|
|
121
145
|
|
|
146
|
+
async function completeJsonAuth(
|
|
147
|
+
context: RouteContext,
|
|
148
|
+
code: string,
|
|
149
|
+
errorPath: string,
|
|
150
|
+
errorParams: Record<string, string | undefined>,
|
|
151
|
+
provider?: 'password' | 'email-code',
|
|
152
|
+
): Promise<Response> {
|
|
153
|
+
const options = getConfig()
|
|
154
|
+
const codeVerifier = getCookie(context.request, `${options.cookiePrefix}_pkce`)
|
|
155
|
+
if (!codeVerifier) {
|
|
156
|
+
queueAuthFailure(context, provider, 'missing_auth_flow')
|
|
157
|
+
return routeResult(
|
|
158
|
+
context,
|
|
159
|
+
errorPath,
|
|
160
|
+
{ ...errorParams, error: 'missing_auth_flow' },
|
|
161
|
+
'missing_auth_flow',
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const tokenResponse = await exchangeCode(context.url, code, codeVerifier, provider)
|
|
166
|
+
if (!tokenResponse) {
|
|
167
|
+
queueAuthFailure(context, provider, 'token_exchange_failed')
|
|
168
|
+
return routeResult(context, errorPath, { ...errorParams, error: 'auth_failed' }, 'auth_failed')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const redirectPath = getCookie(context.request, `${options.cookiePrefix}_redirect`) || '/'
|
|
172
|
+
const redirect = new URL(redirectPath, context.url.origin).toString()
|
|
173
|
+
const response = mutableHeaders(Response.json({ ok: true, redirect }))
|
|
174
|
+
|
|
175
|
+
setTokenCookies(response, options, tokenResponse.accessToken, tokenResponse.refreshToken)
|
|
176
|
+
clearAuthFlowCookies(response, options)
|
|
177
|
+
await setOptionalIdentityCookie(response, tokenResponse.claims.sub)
|
|
178
|
+
if (analyticsAvailable(context)) {
|
|
179
|
+
resolveWaitUntil(context)(
|
|
180
|
+
emitAuthSuccessEvent(context, {
|
|
181
|
+
provider: tokenResponse.provider,
|
|
182
|
+
isNewUser: tokenResponse.isNewUser,
|
|
183
|
+
authPath: context.url.pathname,
|
|
184
|
+
identityUserId: tokenResponse.claims.sub,
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return response
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function queueAuthFailure(
|
|
193
|
+
context: RouteContext,
|
|
194
|
+
provider: AuthAnalyticsProvider | undefined,
|
|
195
|
+
reason: string,
|
|
196
|
+
): void {
|
|
197
|
+
if (!analyticsAvailable(context)) return
|
|
198
|
+
resolveWaitUntil(context)(
|
|
199
|
+
emitAuthFailureEvent(context, {
|
|
200
|
+
provider,
|
|
201
|
+
reason,
|
|
202
|
+
authPath: context.url.pathname,
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function setOptionalIdentityCookie(response: Response, subject: string): Promise<void> {
|
|
208
|
+
const secret = getRuntimeEnv().GL_IDENTITY_SECRET
|
|
209
|
+
if (typeof secret !== 'string' || !secret) return
|
|
210
|
+
try {
|
|
211
|
+
setIdentityCookie(
|
|
212
|
+
response,
|
|
213
|
+
await signIdentityCookie(await createIdentityCookiePayload(subject, secret), secret),
|
|
214
|
+
)
|
|
215
|
+
} catch {
|
|
216
|
+
// Keep login working if the optional analytics identity secret is misconfigured.
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
122
220
|
function authFlowCookieAttrs() {
|
|
123
221
|
return {
|
|
124
222
|
path: '/',
|
package/src/routes/callback.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { getConfig } from '../config-store.js'
|
|
2
2
|
import { getRuntimeEnv } from '../runtime.js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
analyticsAvailable,
|
|
5
|
+
emitAuthFailureEvent,
|
|
6
|
+
emitAuthSuccessEvent,
|
|
7
|
+
} from '../utils/analytics.js'
|
|
4
8
|
import {
|
|
5
9
|
clearAuthFlowCookies,
|
|
6
10
|
getCookie,
|
|
@@ -28,47 +32,98 @@ function getIdentitySecret(): string | null {
|
|
|
28
32
|
return typeof secret === 'string' && secret.length > 0 ? secret : null
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
function wantsJson(request: Request): boolean {
|
|
36
|
+
const accept = request.headers.get('accept') ?? ''
|
|
37
|
+
return accept
|
|
38
|
+
.split(',')
|
|
39
|
+
.some((value) => value.trim().toLowerCase().split(';')[0] === 'application/json')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loginErrorResponse(request: Request, origin: string, loginPath: string): Response {
|
|
43
|
+
const redirect = new URL(`${loginPath}?error=auth_failed`, origin).toString()
|
|
44
|
+
if (wantsJson(request)) {
|
|
45
|
+
return Response.json({ error: 'auth_failed', redirect }, { status: 400 })
|
|
46
|
+
}
|
|
47
|
+
return Response.redirect(redirect, 302)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function queueAuthFailure(
|
|
51
|
+
context: CallbackContext,
|
|
52
|
+
provider: string | null,
|
|
53
|
+
reason: string | null,
|
|
54
|
+
): void {
|
|
55
|
+
if (!analyticsAvailable(context)) return
|
|
56
|
+
const promise = emitAuthFailureEvent(context, {
|
|
57
|
+
provider,
|
|
58
|
+
reason,
|
|
59
|
+
authPath: context.url.pathname,
|
|
60
|
+
})
|
|
61
|
+
resolveWaitUntil(context)(promise)
|
|
62
|
+
}
|
|
63
|
+
|
|
31
64
|
export async function GET(context: CallbackContext): Promise<Response> {
|
|
32
65
|
const options = getConfig()
|
|
33
66
|
const { url, request } = context
|
|
34
67
|
|
|
35
68
|
const code = url.searchParams.get('code')
|
|
36
69
|
const state = url.searchParams.get('state')
|
|
70
|
+
const error = url.searchParams.get('error')
|
|
37
71
|
|
|
38
72
|
// Verify CSRF state
|
|
39
73
|
const storedState = getCookie(request, `${options.cookiePrefix}_state`)
|
|
40
74
|
if (!state || !storedState || !verifyState(state, storedState)) {
|
|
75
|
+
queueAuthFailure(
|
|
76
|
+
context,
|
|
77
|
+
getCookie(request, `${options.cookiePrefix}_provider`),
|
|
78
|
+
'missing_auth_flow',
|
|
79
|
+
)
|
|
41
80
|
return new Response('Invalid state parameter', { status: 403 })
|
|
42
81
|
}
|
|
43
82
|
|
|
44
83
|
// Read PKCE verifier
|
|
45
84
|
const codeVerifier = getCookie(request, `${options.cookiePrefix}_pkce`)
|
|
46
85
|
if (!codeVerifier) {
|
|
86
|
+
queueAuthFailure(
|
|
87
|
+
context,
|
|
88
|
+
getCookie(request, `${options.cookiePrefix}_provider`),
|
|
89
|
+
'missing_auth_flow',
|
|
90
|
+
)
|
|
47
91
|
return new Response('Missing PKCE verifier', { status: 400 })
|
|
48
92
|
}
|
|
49
93
|
|
|
50
94
|
const provider = getCookie(request, `${options.cookiePrefix}_provider`)
|
|
51
95
|
const authPath = getCookie(request, `${options.cookiePrefix}_auth_path`) || url.pathname
|
|
52
96
|
|
|
97
|
+
if (error) {
|
|
98
|
+
queueAuthFailure(context, provider, error)
|
|
99
|
+
return loginErrorResponse(request, url.origin, options.loginPath)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!code) {
|
|
103
|
+
queueAuthFailure(context, provider, 'invalid_request')
|
|
104
|
+
return loginErrorResponse(request, url.origin, options.loginPath)
|
|
105
|
+
}
|
|
106
|
+
|
|
53
107
|
// Exchange code for tokens
|
|
54
108
|
const tokenResponse = await exchangeCode(
|
|
55
109
|
url,
|
|
56
|
-
code
|
|
110
|
+
code,
|
|
57
111
|
codeVerifier,
|
|
58
112
|
provider === 'google' || provider === 'password' || provider === 'email-code'
|
|
59
113
|
? provider
|
|
60
114
|
: undefined,
|
|
61
115
|
)
|
|
62
116
|
if (!tokenResponse) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
302,
|
|
66
|
-
)
|
|
117
|
+
queueAuthFailure(context, provider, 'token_exchange_failed')
|
|
118
|
+
return loginErrorResponse(request, url.origin, options.loginPath)
|
|
67
119
|
}
|
|
68
120
|
|
|
69
121
|
// Build redirect response
|
|
70
122
|
const redirectPath = getCookie(request, `${options.cookiePrefix}_redirect`) || '/'
|
|
71
|
-
const
|
|
123
|
+
const redirect = new URL(redirectPath, url.origin).toString()
|
|
124
|
+
const response = wantsJson(request)
|
|
125
|
+
? Response.json({ ok: true, redirect })
|
|
126
|
+
: Response.redirect(redirect, 302)
|
|
72
127
|
|
|
73
128
|
// Mutable copy for setting cookies
|
|
74
129
|
const mutableResponse = new Response(response.body, {
|
|
@@ -102,12 +157,11 @@ export async function GET(context: CallbackContext): Promise<Response> {
|
|
|
102
157
|
// the root cause of the missing `login_completed` / `signup_completed`
|
|
103
158
|
// rows in `gl_conversion_events` after the Fronts cutover.
|
|
104
159
|
if (analyticsAvailable(context)) {
|
|
105
|
-
const
|
|
106
|
-
const promise = emitAuthEvent(context, eventName, {
|
|
160
|
+
const promise = emitAuthSuccessEvent(context, {
|
|
107
161
|
provider: tokenResponse.provider,
|
|
108
162
|
isNewUser: tokenResponse.isNewUser,
|
|
109
|
-
|
|
110
|
-
|
|
163
|
+
authPath,
|
|
164
|
+
identityUserId: tokenResponse.claims.sub,
|
|
111
165
|
})
|
|
112
166
|
resolveWaitUntil(context)(promise)
|
|
113
167
|
}
|
package/src/routes/flow.ts
CHANGED
|
@@ -5,6 +5,12 @@ import { createAuthFlow } from '../utils/openauth.js'
|
|
|
5
5
|
export interface RouteContext {
|
|
6
6
|
request: Request
|
|
7
7
|
url: URL
|
|
8
|
+
locals?: {
|
|
9
|
+
cfContext?: {
|
|
10
|
+
waitUntil?: (promise: Promise<unknown>) => void
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
waitUntil?: (promise: Promise<unknown>) => void
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
export async function withAuthFlowCookies(
|
package/src/utils/analytics.ts
CHANGED
|
@@ -1,40 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} catch {
|
|
11
|
-
return null
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function resolveAnalytics(): Promise<void> {
|
|
16
|
-
if (resolved) return
|
|
17
|
-
resolved = true
|
|
18
|
-
const mod = await importOptionalModule(ANALYTICS_UTILS_MODULE)
|
|
19
|
-
let candidate: unknown
|
|
20
|
-
try {
|
|
21
|
-
candidate = mod?.trackServerEvent
|
|
22
|
-
} catch {
|
|
23
|
-
candidate = undefined
|
|
24
|
-
}
|
|
25
|
-
if (typeof candidate === 'function') {
|
|
26
|
-
trackFn = candidate as TrackFn
|
|
27
|
-
}
|
|
1
|
+
import { trackServerEvent } from '@growth-labs/analytics/utils'
|
|
2
|
+
|
|
3
|
+
export type AuthAnalyticsProvider = 'google' | 'email-code' | 'password' | 'unknown'
|
|
4
|
+
|
|
5
|
+
export interface AuthSuccessEventInput {
|
|
6
|
+
provider: AuthAnalyticsProvider | string | null | undefined
|
|
7
|
+
isNewUser: boolean
|
|
8
|
+
authPath: string
|
|
9
|
+
identityUserId: string
|
|
28
10
|
}
|
|
29
11
|
|
|
12
|
+
export interface AuthFailureEventInput {
|
|
13
|
+
provider: AuthAnalyticsProvider | string | null | undefined
|
|
14
|
+
reason: string | null | undefined
|
|
15
|
+
authPath: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AUTH_PROVIDERS = new Set(['google', 'email-code', 'password'])
|
|
19
|
+
const AUTH_FAILURE_REASONS = new Set([
|
|
20
|
+
'auth_failed',
|
|
21
|
+
'invalid_credentials',
|
|
22
|
+
'invalid_code',
|
|
23
|
+
'invalid_request',
|
|
24
|
+
'invalid_response',
|
|
25
|
+
'invalid_token',
|
|
26
|
+
'missing_auth_flow',
|
|
27
|
+
'oauth_denied',
|
|
28
|
+
'request_failed',
|
|
29
|
+
'send_failed',
|
|
30
|
+
'token_exchange_failed',
|
|
31
|
+
])
|
|
32
|
+
|
|
30
33
|
export async function emitAuthEvent(
|
|
31
34
|
context: unknown,
|
|
32
35
|
eventName: string,
|
|
33
36
|
data: Record<string, unknown>,
|
|
34
37
|
): Promise<void> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
// Bind via static import so wrangler/esbuild bundles the target at build
|
|
39
|
+
// time. The previous variable-path dynamic import (await import(modulePath))
|
|
40
|
+
// could not be statically analyzed; in Cloudflare Workers the bundle did
|
|
41
|
+
// not include @growth-labs/analytics/utils, the runtime resolved null, and
|
|
42
|
+
// every auth event silently no-op'd — surfaced post-2026-05-22 as
|
|
43
|
+
// `signup_completed` / `login_completed` rows never landing in
|
|
44
|
+
// gl_conversion_events on the new Fronts Worker. See peerDependency.
|
|
45
|
+
await trackServerEvent(context as Parameters<typeof trackServerEvent>[0], eventName, data)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeAuthProvider(
|
|
49
|
+
provider: AuthAnalyticsProvider | string | null | undefined,
|
|
50
|
+
): AuthAnalyticsProvider {
|
|
51
|
+
return provider && AUTH_PROVIDERS.has(provider) ? (provider as AuthAnalyticsProvider) : 'unknown'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeAuthFailureReason(reason: string | null | undefined): string {
|
|
55
|
+
if (reason === 'access_denied') return 'oauth_denied'
|
|
56
|
+
return reason && AUTH_FAILURE_REASONS.has(reason) ? reason : 'auth_failed'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function authSuccessEventName(isNewUser: boolean): 'signup_completed' | 'login_completed' {
|
|
60
|
+
return isNewUser ? 'signup_completed' : 'login_completed'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function emitAuthSuccessEvent(
|
|
64
|
+
context: unknown,
|
|
65
|
+
input: AuthSuccessEventInput,
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
return emitAuthEvent(context, authSuccessEventName(input.isNewUser), {
|
|
68
|
+
provider: normalizeAuthProvider(input.provider),
|
|
69
|
+
isNewUser: input.isNewUser,
|
|
70
|
+
auth_path: input.authPath,
|
|
71
|
+
identity_user_id: input.identityUserId,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function emitAuthFailureEvent(
|
|
76
|
+
context: unknown,
|
|
77
|
+
input: AuthFailureEventInput,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
return emitAuthEvent(context, 'auth_failed', {
|
|
80
|
+
provider: normalizeAuthProvider(input.provider),
|
|
81
|
+
reason: normalizeAuthFailureReason(input.reason),
|
|
82
|
+
auth_path: input.authPath,
|
|
83
|
+
})
|
|
38
84
|
}
|
|
39
85
|
|
|
40
86
|
export function analyticsAvailable(context: { locals?: Record<string, unknown> }): boolean {
|
|
@@ -50,7 +96,9 @@ export async function trackAuthEvent(
|
|
|
50
96
|
return emitAuthEvent(context, eventName, data)
|
|
51
97
|
}
|
|
52
98
|
|
|
99
|
+
// Test-only export retained for compatibility with existing tests that reset
|
|
100
|
+
// internal state between runs. Now a no-op since there's no internal state
|
|
101
|
+
// to reset — trackFn is the bound static import.
|
|
53
102
|
export function _resetAnalytics(): void {
|
|
54
|
-
|
|
55
|
-
resolved = false
|
|
103
|
+
// noop
|
|
56
104
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
type AuthAnalyticsProvider,
|
|
3
|
+
type AuthFailureEventInput,
|
|
4
|
+
type AuthSuccessEventInput,
|
|
5
|
+
authSuccessEventName,
|
|
6
|
+
emitAuthEvent,
|
|
7
|
+
emitAuthFailureEvent,
|
|
8
|
+
emitAuthSuccessEvent,
|
|
9
|
+
normalizeAuthFailureReason,
|
|
10
|
+
normalizeAuthProvider,
|
|
11
|
+
trackAuthEvent,
|
|
12
|
+
} from './analytics.js'
|
|
2
13
|
export {
|
|
3
14
|
type BridgeMigrationOptions,
|
|
4
15
|
type BridgeMigrationStatement,
|