@warlock.js/auth 5.29.0 → 5.31.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/CHANGELOG.md +23 -0
- package/esm/commands/auth-cleanup-command.d.mts +1 -1
- package/esm/commands/auth-cleanup-command.d.mts.map +1 -1
- package/esm/commands/auth-cleanup-command.mjs +1 -1
- package/esm/commands/auth-cleanup-command.mjs.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.d.mts +1 -1
- package/esm/commands/auth-purge-never-expiring-command.d.mts.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.mjs +1 -1
- package/esm/commands/auth-purge-never-expiring-command.mjs.map +1 -1
- package/esm/commands/jwt-secret-generator-command.d.mts +1 -1
- package/esm/commands/jwt-secret-generator-command.d.mts.map +1 -1
- package/esm/commands/jwt-secret-generator-command.mjs +1 -1
- package/esm/commands/jwt-secret-generator-command.mjs.map +1 -1
- package/esm/contracts/auth-contract.d.mts +1 -1
- package/esm/contracts/auth-contract.d.mts.map +1 -1
- package/esm/contracts/providers.d.mts +1 -1
- package/esm/contracts/providers.d.mts.map +1 -1
- package/esm/contracts/request-locals.d.mts +1 -1
- package/esm/contracts/request-locals.d.mts.map +1 -1
- package/esm/contracts/types.d.mts +1 -1
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.mjs +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -1
- package/esm/errors/email-not-verified.error.d.mts +1 -1
- package/esm/errors/email-not-verified.error.d.mts.map +1 -1
- package/esm/errors/email-not-verified.error.mjs +1 -1
- package/esm/errors/email-not-verified.error.mjs.map +1 -1
- package/esm/errors/invalid-one-time-token.error.d.mts +1 -1
- package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -1
- package/esm/errors/invalid-one-time-token.error.mjs +1 -1
- package/esm/errors/invalid-one-time-token.error.mjs.map +1 -1
- package/esm/errors/invalid-passkey.error.d.mts +1 -1
- package/esm/errors/invalid-passkey.error.d.mts.map +1 -1
- package/esm/errors/invalid-passkey.error.mjs +1 -1
- package/esm/errors/invalid-passkey.error.mjs.map +1 -1
- package/esm/errors/invalid-provider-callback.error.d.mts +1 -1
- package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -1
- package/esm/errors/invalid-provider-callback.error.mjs +1 -1
- package/esm/errors/invalid-provider-callback.error.mjs.map +1 -1
- package/esm/errors/notifications-unavailable.error.d.mts +1 -1
- package/esm/errors/notifications-unavailable.error.d.mts.map +1 -1
- package/esm/errors/notifications-unavailable.error.mjs +1 -1
- package/esm/errors/notifications-unavailable.error.mjs.map +1 -1
- package/esm/errors/provider-email-not-verified.error.d.mts +1 -1
- package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -1
- package/esm/errors/provider-email-not-verified.error.mjs +1 -1
- package/esm/errors/provider-email-not-verified.error.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/localization/ar.translations.mjs +1 -1
- package/esm/localization/ar.translations.mjs.map +1 -1
- package/esm/localization/auth-default-translations.d.mts +1 -1
- package/esm/localization/auth-default-translations.d.mts.map +1 -1
- package/esm/localization/auth-default-translations.mjs +1 -1
- package/esm/localization/auth-default-translations.mjs.map +1 -1
- package/esm/localization/auth-translations.type.d.mts +1 -1
- package/esm/localization/auth-translations.type.d.mts.map +1 -1
- package/esm/localization/en.translations.mjs +1 -1
- package/esm/localization/en.translations.mjs.map +1 -1
- package/esm/localization/init.mjs +1 -1
- package/esm/localization/init.mjs.map +1 -1
- package/esm/localization/register-auth-translations.d.mts +1 -1
- package/esm/localization/register-auth-translations.d.mts.map +1 -1
- package/esm/localization/register-auth-translations.mjs +1 -1
- package/esm/localization/register-auth-translations.mjs.map +1 -1
- package/esm/middleware/auth.middleware.d.mts +1 -1
- package/esm/middleware/auth.middleware.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.mjs +3 -3
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/middleware/csrf-origin-check.d.mts +1 -1
- package/esm/middleware/csrf-origin-check.d.mts.map +1 -1
- package/esm/middleware/csrf-origin-check.mjs +1 -1
- package/esm/middleware/csrf-origin-check.mjs.map +1 -1
- package/esm/middleware/index.d.mts +1 -1
- package/esm/middleware/index.mjs +1 -1
- package/esm/middleware/localized-login-path.mjs +1 -1
- package/esm/middleware/localized-login-path.mjs.map +1 -1
- package/esm/middleware/login-throttle.middleware.d.mts +17 -2
- package/esm/middleware/login-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/login-throttle.middleware.mjs +84 -6
- package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.d.mts +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.mjs +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/otp-throttle.middleware.d.mts +1 -1
- package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/otp-throttle.middleware.mjs +1 -1
- package/esm/middleware/otp-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/require-verified-email.middleware.d.mts +1 -1
- package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -1
- package/esm/middleware/require-verified-email.middleware.mjs +1 -1
- package/esm/middleware/require-verified-email.middleware.mjs.map +1 -1
- package/esm/models/access-token/access-token.model.d.mts +3 -35
- package/esm/models/access-token/access-token.model.d.mts.map +1 -1
- package/esm/models/access-token/access-token.model.mjs +1 -1
- package/esm/models/access-token/access-token.model.mjs.map +1 -1
- package/esm/models/access-token/family-association.migration.mjs +1 -1
- package/esm/models/access-token/family-association.migration.mjs.map +1 -1
- package/esm/models/access-token/migration.mjs +1 -1
- package/esm/models/access-token/migration.mjs.map +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.d.mts +3 -43
- package/esm/models/auth-token-family/auth-token-family.model.d.mts.map +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.mjs +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.mjs.map +1 -1
- package/esm/models/auth-token-family/migration.mjs +1 -1
- package/esm/models/auth-token-family/migration.mjs.map +1 -1
- package/esm/models/auth.model.d.mts +1 -1
- package/esm/models/auth.model.d.mts.map +1 -1
- package/esm/models/auth.model.mjs +1 -1
- package/esm/models/auth.model.mjs.map +1 -1
- package/esm/models/index.d.mts +1 -1
- package/esm/models/index.d.mts.map +1 -1
- package/esm/models/index.mjs +1 -1
- package/esm/models/index.mjs.map +1 -1
- package/esm/models/one-time-token/migration.mjs +1 -1
- package/esm/models/one-time-token/migration.mjs.map +1 -1
- package/esm/models/one-time-token/one-time-token.model.d.mts +3 -67
- package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -1
- package/esm/models/one-time-token/one-time-token.model.mjs +1 -1
- package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -1
- package/esm/models/passkey-credential/migration.mjs +1 -1
- package/esm/models/passkey-credential/migration.mjs.map +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.d.mts +3 -47
- package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.mjs +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -1
- package/esm/models/provider-account/migration.mjs +1 -1
- package/esm/models/provider-account/migration.mjs.map +1 -1
- package/esm/models/provider-account/provider-account.model.d.mts +3 -35
- package/esm/models/provider-account/provider-account.model.d.mts.map +1 -1
- package/esm/models/provider-account/provider-account.model.mjs +1 -1
- package/esm/models/provider-account/provider-account.model.mjs.map +1 -1
- package/esm/models/refresh-token/migration.mjs +1 -1
- package/esm/models/refresh-token/migration.mjs.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.d.mts +7 -99
- package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
- package/esm/models/refresh-token/successor-association.migration.mjs +1 -1
- package/esm/models/refresh-token/successor-association.migration.mjs.map +1 -1
- package/esm/otp/otp.d.mts +1 -1
- package/esm/otp/otp.d.mts.map +1 -1
- package/esm/otp/otp.mjs +1 -1
- package/esm/otp/otp.mjs.map +1 -1
- package/esm/passkeys/passkey-authentication.d.mts +1 -1
- package/esm/passkeys/passkey-authentication.d.mts.map +1 -1
- package/esm/passkeys/passkey-authentication.mjs +1 -1
- package/esm/passkeys/passkey-authentication.mjs.map +1 -1
- package/esm/passkeys/passkey-challenges.mjs +1 -1
- package/esm/passkeys/passkey-challenges.mjs.map +1 -1
- package/esm/passkeys/passkey-registration.d.mts +1 -1
- package/esm/passkeys/passkey-registration.d.mts.map +1 -1
- package/esm/passkeys/passkey-registration.mjs +1 -1
- package/esm/passkeys/passkey-registration.mjs.map +1 -1
- package/esm/passkeys/simplewebauthn.d.mts +1 -1
- package/esm/passkeys/simplewebauthn.d.mts.map +1 -1
- package/esm/passkeys/simplewebauthn.mjs +1 -1
- package/esm/passkeys/simplewebauthn.mjs.map +1 -1
- package/esm/providers/apple-provider.d.mts +1 -1
- package/esm/providers/apple-provider.d.mts.map +1 -1
- package/esm/providers/apple-provider.mjs +1 -1
- package/esm/providers/apple-provider.mjs.map +1 -1
- package/esm/providers/discord-provider.d.mts +1 -1
- package/esm/providers/discord-provider.d.mts.map +1 -1
- package/esm/providers/discord-provider.mjs +1 -1
- package/esm/providers/discord-provider.mjs.map +1 -1
- package/esm/providers/facebook-provider.d.mts +1 -1
- package/esm/providers/facebook-provider.d.mts.map +1 -1
- package/esm/providers/facebook-provider.mjs +1 -1
- package/esm/providers/facebook-provider.mjs.map +1 -1
- package/esm/providers/github-provider.d.mts +1 -1
- package/esm/providers/github-provider.d.mts.map +1 -1
- package/esm/providers/github-provider.mjs +1 -1
- package/esm/providers/github-provider.mjs.map +1 -1
- package/esm/providers/google-provider.d.mts +1 -1
- package/esm/providers/google-provider.d.mts.map +1 -1
- package/esm/providers/google-provider.mjs +1 -1
- package/esm/providers/google-provider.mjs.map +1 -1
- package/esm/providers/linkedin-provider.d.mts +1 -1
- package/esm/providers/linkedin-provider.d.mts.map +1 -1
- package/esm/providers/linkedin-provider.mjs +1 -1
- package/esm/providers/linkedin-provider.mjs.map +1 -1
- package/esm/providers/provider-login.d.mts +1 -1
- package/esm/providers/provider-login.d.mts.map +1 -1
- package/esm/providers/provider-login.mjs +1 -1
- package/esm/providers/provider-login.mjs.map +1 -1
- package/esm/providers/provider-state-cookie.mjs +1 -1
- package/esm/providers/provider-state-cookie.mjs.map +1 -1
- package/esm/providers/resolve-provider-user.mjs +1 -1
- package/esm/providers/resolve-provider-user.mjs.map +1 -1
- package/esm/providers/x-provider.d.mts +1 -1
- package/esm/providers/x-provider.d.mts.map +1 -1
- package/esm/providers/x-provider.mjs +1 -1
- package/esm/providers/x-provider.mjs.map +1 -1
- package/esm/services/auth-config.mjs +1 -1
- package/esm/services/auth-config.mjs.map +1 -1
- package/esm/services/auth-events.d.mts +1 -1
- package/esm/services/auth-events.d.mts.map +1 -1
- package/esm/services/auth-events.mjs +1 -1
- package/esm/services/auth-events.mjs.map +1 -1
- package/esm/services/auth.service.d.mts +1 -1
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs +1 -1
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/services/current-user.d.mts +1 -1
- package/esm/services/current-user.d.mts.map +1 -1
- package/esm/services/current-user.mjs +1 -1
- package/esm/services/current-user.mjs.map +1 -1
- package/esm/services/email-verification.d.mts +1 -1
- package/esm/services/email-verification.d.mts.map +1 -1
- package/esm/services/email-verification.mjs +1 -1
- package/esm/services/email-verification.mjs.map +1 -1
- package/esm/services/generate-jwt-secret.d.mts +1 -1
- package/esm/services/generate-jwt-secret.d.mts.map +1 -1
- package/esm/services/generate-jwt-secret.mjs +1 -1
- package/esm/services/generate-jwt-secret.mjs.map +1 -1
- package/esm/services/jwt.d.mts +1 -1
- package/esm/services/jwt.d.mts.map +1 -1
- package/esm/services/jwt.mjs +1 -1
- package/esm/services/jwt.mjs.map +1 -1
- package/esm/services/one-time-tokens.d.mts +1 -1
- package/esm/services/one-time-tokens.d.mts.map +1 -1
- package/esm/services/one-time-tokens.mjs +1 -1
- package/esm/services/one-time-tokens.mjs.map +1 -1
- package/esm/services/optional-peer.mjs +1 -1
- package/esm/services/optional-peer.mjs.map +1 -1
- package/esm/services/password-reset.d.mts +1 -1
- package/esm/services/password-reset.d.mts.map +1 -1
- package/esm/services/password-reset.mjs +1 -1
- package/esm/services/password-reset.mjs.map +1 -1
- package/esm/services/resolve-request-user.d.mts +1 -1
- package/esm/services/resolve-request-user.d.mts.map +1 -1
- package/esm/services/resolve-request-user.mjs +1 -1
- package/esm/services/resolve-request-user.mjs.map +1 -1
- package/esm/services/token-family-operation.mjs +1 -1
- package/esm/services/token-family-operation.mjs.map +1 -1
- package/esm/services/token-notifications.mjs +1 -1
- package/esm/services/token-notifications.mjs.map +1 -1
- package/esm/session/page-session.d.mts +1 -1
- package/esm/session/page-session.d.mts.map +1 -1
- package/esm/session/page-session.mjs +1 -1
- package/esm/session/page-session.mjs.map +1 -1
- package/esm/utils/auth-error-codes.d.mts +1 -1
- package/esm/utils/auth-error-codes.d.mts.map +1 -1
- package/esm/utils/auth-error-codes.mjs +1 -1
- package/esm/utils/auth-error-codes.mjs.map +1 -1
- package/esm/utils/safe-equal.d.mts +1 -1
- package/esm/utils/safe-equal.d.mts.map +1 -1
- package/esm/utils/safe-equal.mjs +1 -1
- package/esm/utils/safe-equal.mjs.map +1 -1
- package/esm/utils/token-expiry.d.mts +1 -1
- package/esm/utils/token-expiry.d.mts.map +1 -1
- package/esm/utils/token-expiry.mjs +1 -1
- package/esm/utils/token-expiry.mjs.map +1 -1
- package/llms-full.txt +192 -112
- package/package.json +10 -9
- package/skills/auth-basics/SKILL.md +10 -13
- package/skills/customize-token-storage/SKILL.md +24 -19
- package/skills/customize-user-type/SKILL.md +3 -1
- package/skills/index.md +4 -1
- package/skills/login-with-providers/SKILL.md +54 -29
- package/skills/manage-tokens/SKILL.md +2 -2
- package/skills/overview/SKILL.md +10 -1
- package/skills/protect-routes/SKILL.md +23 -14
- package/skills/register-user/SKILL.md +4 -4
- package/skills/run-auth-commands/SKILL.md +1 -1
- package/skills/throttle-login-attempts/SKILL.md +54 -23
- package/skills/verify-email-and-reset-password/SKILL.md +7 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facebook-provider.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"facebook-provider.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/facebook-provider.ts"],"sourcesContent":["import type {\n AuthProvider,\n FacebookProviderConfig,\n ProviderAuthorizationState,\n ProviderCallbackParams,\n ProviderProfile,\n} from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { pkceChallenge } from \"./google-provider\";\n\nconst AUTHORIZATION_ENDPOINT = \"https://www.facebook.com/v19.0/dialog/oauth\";\nconst TOKEN_ENDPOINT = \"https://graph.facebook.com/v19.0/oauth/access_token\";\nconst USER_ENDPOINT = \"https://graph.facebook.com/v19.0/me\";\nconst DEFAULT_SCOPES = [\"email\", \"public_profile\"];\n\ntype FacebookUser = {\n id: string;\n name?: string;\n email?: string;\n picture?: { data?: { url?: string } };\n};\n\n/**\n * Facebook sign-in: plain OAuth 2 authorization code flow with PKCE (S256).\n * Facebook is not OpenID Connect — there is no `id_token`. Unlike every other\n * built-in provider, Facebook's token endpoint is redeemed with a `GET`\n * request (query-string parameters, not a POST body) — that shape comes\n * straight from Facebook's Graph API docs, not from a live exchange (this\n * package has no Facebook test app). The profile is then read once from\n * `Graph /me?fields=id,name,email,picture`, over a direct HTTPS call; no\n * Facebook SDK is involved.\n *\n * Facebook only ever returns a confirmed address for `email` — there is no\n * separate verified flag — so an email present on the profile is treated as\n * verified. An app without `email` permission granted, or one that simply has\n * no email on file, gets `email: undefined`, which `resolveProviderUser`\n * already rejects as an unverified/missing email, the same path GitHub's\n * unverified addresses take.\n */\nexport class FacebookProvider implements AuthProvider {\n public readonly name = \"facebook\";\n\n public constructor(private readonly settings: FacebookProviderConfig) {}\n\n public authorizationUrl(state: ProviderAuthorizationState): string {\n const url = new URL(AUTHORIZATION_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"scope\", (this.settings.scopes ?? DEFAULT_SCOPES).join(\" \"));\n url.searchParams.set(\"state\", state.state);\n url.searchParams.set(\"code_challenge\", pkceChallenge(state.codeVerifier));\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n\n return url.toString();\n }\n\n public async handleCallback({\n query,\n expected,\n }: ProviderCallbackParams): Promise<ProviderProfile> {\n const code = query.code;\n\n if (typeof code !== \"string\" || code.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-code\");\n }\n\n const accessToken = await this.exchangeCode(code, expected.codeVerifier);\n const user = await this.fetchUser(accessToken);\n\n if (typeof user.id !== \"string\" || user.id.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-subject\");\n }\n\n return {\n provider: this.name,\n providerUserId: user.id,\n email: user.email,\n // Facebook does not report a verification flag; a returned address is confirmed.\n emailVerified: typeof user.email === \"string\" && user.email.length > 0,\n name: user.name,\n avatar: user.picture?.data?.url,\n raw: user,\n };\n }\n\n /** Redeem the authorization code (with the PKCE verifier) for an access token. */\n private async exchangeCode(code: string, codeVerifier: string): Promise<string> {\n let body: { access_token?: unknown; error?: unknown };\n\n try {\n const url = new URL(TOKEN_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"client_secret\", this.settings.clientSecret);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"code\", code);\n url.searchParams.set(\"code_verifier\", codeVerifier);\n\n const response = await fetch(url.toString(), { headers: { accept: \"application/json\" } });\n\n if (!response.ok) {\n throw new Error(`Facebook token endpoint answered ${response.status}`);\n }\n\n body = (await response.json()) as { access_token?: unknown; error?: unknown };\n } catch (error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", error);\n }\n\n if (body.error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", body.error);\n }\n\n if (typeof body.access_token !== \"string\" || body.access_token.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-access-token\");\n }\n\n return body.access_token;\n }\n\n private async fetchUser(accessToken: string): Promise<FacebookUser> {\n try {\n const url = new URL(USER_ENDPOINT);\n\n url.searchParams.set(\"fields\", \"id,name,email,picture\");\n url.searchParams.set(\"access_token\", accessToken);\n\n const response = await fetch(url.toString(), { headers: { accept: \"application/json\" } });\n\n if (!response.ok) {\n throw new Error(`Facebook /me endpoint answered ${response.status}`);\n }\n\n return (await response.json()) as FacebookUser;\n } catch (error) {\n throw new InvalidProviderCallbackError(\"profile-fetch-failed\", error);\n }\n }\n}\n"],"mappings":";;;;AAUA,MAAM,yBAAyB;AAC/B,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AACtB,MAAM,iBAAiB,CAAC,SAAS,gBAAgB;;;;;;;;;;;;;;;;;;AA0BjD,IAAa,mBAAb,MAAsD;CAGpD,AAAO,YAAY,AAAiB,UAAkC;EAAlC;cAFb;CAEgD;CAEvE,AAAO,iBAAiB,OAA2C;EACjE,MAAM,MAAM,IAAI,IAAI,sBAAsB;EAE1C,IAAI,aAAa,IAAI,aAAa,KAAK,SAAS,QAAQ;EACxD,IAAI,aAAa,IAAI,gBAAgB,KAAK,SAAS,WAAW;EAC9D,IAAI,aAAa,IAAI,iBAAiB,MAAM;EAC5C,IAAI,aAAa,IAAI,UAAU,KAAK,SAAS,UAAU,eAAc,CAAE,KAAK,GAAG,CAAC;EAChF,IAAI,aAAa,IAAI,SAAS,MAAM,KAAK;EACzC,IAAI,aAAa,IAAI,kBAAkB,cAAc,MAAM,YAAY,CAAC;EACxE,IAAI,aAAa,IAAI,yBAAyB,MAAM;EAEpD,OAAO,IAAI,SAAS;CACtB;CAEA,MAAa,eAAe,EAC1B,OACA,YACmD;EACnD,MAAM,OAAO,MAAM;EAEnB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,6BAA6B,cAAc;EAGvD,MAAM,cAAc,MAAM,KAAK,aAAa,MAAM,SAAS,YAAY;EACvE,MAAM,OAAO,MAAM,KAAK,UAAU,WAAW;EAE7C,IAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,WAAW,GACpD,MAAM,IAAI,6BAA6B,iBAAiB;EAG1D,OAAO;GACL,UAAU,KAAK;GACf,gBAAgB,KAAK;GACrB,OAAO,KAAK;GAEZ,eAAe,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,SAAS;GACrE,MAAM,KAAK;GACX,QAAQ,KAAK,SAAS,MAAM;GAC5B,KAAK;EACP;CACF;;CAGA,MAAc,aAAa,MAAc,cAAuC;EAC9E,IAAI;EAEJ,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,cAAc;GAElC,IAAI,aAAa,IAAI,aAAa,KAAK,SAAS,QAAQ;GACxD,IAAI,aAAa,IAAI,iBAAiB,KAAK,SAAS,YAAY;GAChE,IAAI,aAAa,IAAI,gBAAgB,KAAK,SAAS,WAAW;GAC9D,IAAI,aAAa,IAAI,QAAQ,IAAI;GACjC,IAAI,aAAa,IAAI,iBAAiB,YAAY;GAElD,MAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;GAExF,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,oCAAoC,SAAS,QAAQ;GAGvE,OAAQ,MAAM,SAAS,KAAK;EAC9B,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,wBAAwB,KAAK;EACtE;EAEA,IAAI,KAAK,OACP,MAAM,IAAI,6BAA6B,wBAAwB,KAAK,KAAK;EAG3E,IAAI,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,WAAW,GACxE,MAAM,IAAI,6BAA6B,sBAAsB;EAG/D,OAAO,KAAK;CACd;CAEA,MAAc,UAAU,aAA4C;EAClE,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,aAAa;GAEjC,IAAI,aAAa,IAAI,UAAU,uBAAuB;GACtD,IAAI,aAAa,IAAI,gBAAgB,WAAW;GAEhD,MAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;GAExF,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,kCAAkC,SAAS,QAAQ;GAGrE,OAAQ,MAAM,SAAS,KAAK;EAC9B,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,wBAAwB,KAAK;EACtE;CACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthProvider, GitHubProviderConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile } from "../contracts/providers.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/providers/github-provider.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* GitHub sign-in: plain OAuth 2 authorization code flow with PKCE (S256).
|
|
6
6
|
* GitHub is not OpenID Connect — there is no `id_token` and nothing to verify
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-provider.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"github-provider.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/providers/github-provider.ts"],"mappings":";;;;;AA0CA;;;;;;;;cAAa,cAAA,YAA0B,YAAA;EAAA,iBAGD,QAAA;EAAA,SAFpB,IAAA;cAEoB,QAAA,EAAU,oBAAA;EAEvC,gBAAA,CAAiB,KAAA,EAAO,0BAAA;EAclB,cAAA;IACX,KAAA;IACA;EAAA,GACC,sBAAA,GAAyB,OAAA,CAAQ,eAAA;EAnBA;EAAA,QA8CtB,YAAA;EAAA,QAwCA,SAAA;EAtFgC;EAAA,QA2GhC,iBAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InvalidProviderCallbackError } from "../errors/invalid-provider-callback.error.mjs";
|
|
2
2
|
import { pkceChallenge } from "./google-provider.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/providers/github-provider.ts
|
|
5
5
|
const AUTHORIZATION_ENDPOINT = "https://github.com/login/oauth/authorize";
|
|
6
6
|
const TOKEN_ENDPOINT = "https://github.com/login/oauth/access_token";
|
|
7
7
|
const USER_ENDPOINT = "https://api.github.com/user";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-provider.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"github-provider.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/github-provider.ts"],"sourcesContent":["import type {\n AuthProvider,\n GitHubProviderConfig,\n ProviderAuthorizationState,\n ProviderCallbackParams,\n ProviderProfile,\n} from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { pkceChallenge } from \"./google-provider\";\n\nconst AUTHORIZATION_ENDPOINT = \"https://github.com/login/oauth/authorize\";\nconst TOKEN_ENDPOINT = \"https://github.com/login/oauth/access_token\";\nconst USER_ENDPOINT = \"https://api.github.com/user\";\nconst EMAILS_ENDPOINT = \"https://api.github.com/user/emails\";\nconst DEFAULT_SCOPES = [\"read:user\", \"user:email\"];\n\n/** GitHub requires a `User-Agent`; identify the framework, not the app. */\nconst USER_AGENT = \"warlock.js-auth\";\n\ntype GitHubUser = {\n id: number;\n login: string;\n name?: string | null;\n avatar_url?: string;\n};\n\ntype GitHubEmail = {\n email: string;\n primary: boolean;\n verified: boolean;\n};\n\n/**\n * GitHub sign-in: plain OAuth 2 authorization code flow with PKCE (S256).\n * GitHub is not OpenID Connect — there is no `id_token` and nothing to verify\n * against a JWKS. The access token is redeemed once for the profile, over a\n * direct HTTPS call to GitHub's API; no vendor SDK is involved.\n *\n * The account email is fetched separately from `/user/emails`, because\n * `/user.email` is `null` unless the user made it public — the emails\n * endpoint is also where GitHub reports whether an address is verified.\n */\nexport class GitHubProvider implements AuthProvider {\n public readonly name = \"github\";\n\n public constructor(private readonly settings: GitHubProviderConfig) {}\n\n public authorizationUrl(state: ProviderAuthorizationState): string {\n const url = new URL(AUTHORIZATION_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"scope\", (this.settings.scopes ?? DEFAULT_SCOPES).join(\" \"));\n url.searchParams.set(\"state\", state.state);\n url.searchParams.set(\"code_challenge\", pkceChallenge(state.codeVerifier));\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n\n return url.toString();\n }\n\n public async handleCallback({\n query,\n expected,\n }: ProviderCallbackParams): Promise<ProviderProfile> {\n const code = query.code;\n\n if (typeof code !== \"string\" || code.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-code\");\n }\n\n const accessToken = await this.exchangeCode(code, expected.codeVerifier);\n const user = await this.fetchUser(accessToken);\n const email = await this.fetchPrimaryEmail(accessToken);\n\n if (typeof user.id !== \"number\") {\n throw new InvalidProviderCallbackError(\"missing-subject\");\n }\n\n return {\n provider: this.name,\n providerUserId: String(user.id),\n email: email?.email,\n emailVerified: email?.verified === true,\n name: user.name ?? user.login,\n avatar: user.avatar_url,\n raw: user,\n };\n }\n\n /** Redeem the authorization code (with the PKCE verifier) for an access token. */\n private async exchangeCode(code: string, codeVerifier: string): Promise<string> {\n let body: { access_token?: unknown; error?: unknown };\n\n try {\n const response = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n accept: \"application/json\",\n },\n body: new URLSearchParams({\n code,\n client_id: this.settings.clientId,\n client_secret: this.settings.clientSecret,\n redirect_uri: this.settings.redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`GitHub token endpoint answered ${response.status}`);\n }\n\n body = (await response.json()) as { access_token?: unknown; error?: unknown };\n } catch (error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", error);\n }\n\n // GitHub answers 200 even on a rejected code, with `error` set instead.\n if (body.error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", body.error);\n }\n\n if (typeof body.access_token !== \"string\" || body.access_token.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-access-token\");\n }\n\n return body.access_token;\n }\n\n private async fetchUser(accessToken: string): Promise<GitHubUser> {\n try {\n const response = await fetch(USER_ENDPOINT, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n accept: \"application/vnd.github+json\",\n \"user-agent\": USER_AGENT,\n },\n });\n\n if (!response.ok) {\n throw new Error(`GitHub user endpoint answered ${response.status}`);\n }\n\n return (await response.json()) as GitHubUser;\n } catch (error) {\n throw new InvalidProviderCallbackError(\"profile-fetch-failed\", error);\n }\n }\n\n /** The primary, verified email — `undefined` when none is both primary and verified. */\n private async fetchPrimaryEmail(accessToken: string): Promise<GitHubEmail | undefined> {\n let emails: GitHubEmail[];\n\n try {\n const response = await fetch(EMAILS_ENDPOINT, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n accept: \"application/vnd.github+json\",\n \"user-agent\": USER_AGENT,\n },\n });\n\n if (!response.ok) {\n throw new Error(`GitHub emails endpoint answered ${response.status}`);\n }\n\n emails = (await response.json()) as GitHubEmail[];\n } catch (error) {\n throw new InvalidProviderCallbackError(\"profile-fetch-failed\", error);\n }\n\n return emails.find((email) => email.primary && email.verified);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthProvider, GoogleProviderConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile } from "../contracts/providers.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/providers/google-provider.d.ts
|
|
4
4
|
/** The `warlock add` feature that installs `jose`. */
|
|
5
5
|
declare const GOOGLE_AUTH_FEATURE = "auth-google";
|
|
6
6
|
/** The slice of `jose` the Google provider uses — declared locally because `jose` is an optional peer. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-provider.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"google-provider.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/providers/google-provider.ts"],"mappings":";;;;cAkBa,mBAAA;AAAb;AAAA,KAGY,UAAA;EACV,kBAAA,GAAqB,GAAA,EAAK,GAAA;EAC1B,SAAA,GACE,KAAA,UACA,GAAA,WACA,OAAA;IAAW,MAAA;IAAkB,QAAA;EAAA,MAC1B,OAAA;IAAU,OAAA,EAAS,MAAA;EAAA;AAAA;;iBAOV,aAAA,CAAc,YAAoB;;;;;;;cAUrC,cAAA,YAA0B,YAAA;EAAA,iBAGD,QAAA;EAAA,SAFpB,IAAA;cAEoB,QAAA,EAAU,oBAAA;EAEvC,gBAAA,CAAiB,KAAA,EAAO,0BAAA;EAelB,cAAA;IACX,KAAA;IACA;EAAA,GACC,sBAAA,GAAyB,OAAA,CAAQ,eAAA;EAxCZ;EAAA,QA0EV,YAAA;EA1EgB;EAAA,QA4GhB,aAAA;AAAA"}
|
|
@@ -2,7 +2,7 @@ import { InvalidProviderCallbackError } from "../errors/invalid-provider-callbac
|
|
|
2
2
|
import { loadOptionalPeer } from "../services/optional-peer.mjs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/providers/google-provider.ts
|
|
6
6
|
const AUTHORIZATION_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
7
7
|
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
8
8
|
const JWKS_URI = "https://www.googleapis.com/oauth2/v3/certs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-provider.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"google-provider.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/google-provider.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type {\n AuthProvider,\n GoogleProviderConfig,\n ProviderAuthorizationState,\n ProviderCallbackParams,\n ProviderProfile,\n} from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { loadOptionalPeer } from \"../services/optional-peer\";\n\nconst AUTHORIZATION_ENDPOINT = \"https://accounts.google.com/o/oauth2/v2/auth\";\nconst TOKEN_ENDPOINT = \"https://oauth2.googleapis.com/token\";\nconst JWKS_URI = \"https://www.googleapis.com/oauth2/v3/certs\";\nconst ISSUERS = [\"https://accounts.google.com\", \"accounts.google.com\"];\nconst DEFAULT_SCOPES = [\"openid\", \"email\", \"profile\"];\n\n/** The `warlock add` feature that installs `jose`. */\nexport const GOOGLE_AUTH_FEATURE = \"auth-google\";\n\n/** The slice of `jose` the Google provider uses — declared locally because `jose` is an optional peer. */\nexport type JoseModule = {\n createRemoteJWKSet: (url: URL) => unknown;\n jwtVerify: (\n token: string,\n key: unknown,\n options: { issuer: string[]; audience: string },\n ) => Promise<{ payload: Record<string, unknown> }>;\n};\n\n/** Google's key set, created once per process; `jose` caches the fetched JWKS inside it. */\nlet googleKeySet: unknown;\n\n/** PKCE S256 code challenge for a verifier. */\nexport function pkceChallenge(codeVerifier: string): string {\n return createHash(\"sha256\").update(codeVerifier).digest(\"base64url\");\n}\n\n/**\n * Google sign-in: OpenID Connect authorization code flow with PKCE (S256),\n * `state` and `nonce`. The code is exchanged with plain `fetch`; the id_token\n * is verified with `jose` against Google's JWKS (signature, `iss`, `aud` = the\n * client id, `exp`), then its `nonce` is compared. No Google SDK.\n */\nexport class GoogleProvider implements AuthProvider {\n public readonly name = \"google\";\n\n public constructor(private readonly settings: GoogleProviderConfig) {}\n\n public authorizationUrl(state: ProviderAuthorizationState): string {\n const url = new URL(AUTHORIZATION_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"scope\", (this.settings.scopes ?? DEFAULT_SCOPES).join(\" \"));\n url.searchParams.set(\"state\", state.state);\n url.searchParams.set(\"nonce\", state.nonce);\n url.searchParams.set(\"code_challenge\", pkceChallenge(state.codeVerifier));\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n\n return url.toString();\n }\n\n public async handleCallback({\n query,\n expected,\n }: ProviderCallbackParams): Promise<ProviderProfile> {\n // Load the peer first: a missing SDK fails loudly before any network call.\n const jose = await loadOptionalPeer<JoseModule>(\"jose\", GOOGLE_AUTH_FEATURE);\n\n const code = query.code;\n\n if (typeof code !== \"string\" || code.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-code\");\n }\n\n const idToken = await this.exchangeCode(code, expected.codeVerifier);\n const claims = await this.verifyIdToken(jose, idToken);\n\n if (typeof claims.nonce !== \"string\" || claims.nonce !== expected.nonce) {\n throw new InvalidProviderCallbackError(\"nonce-mismatch\");\n }\n\n if (typeof claims.sub !== \"string\" || claims.sub.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-subject\");\n }\n\n return {\n provider: this.name,\n providerUserId: claims.sub,\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n // Google sends a boolean; anything else counts as NOT verified.\n emailVerified: claims.email_verified === true,\n name: typeof claims.name === \"string\" ? claims.name : undefined,\n avatar: typeof claims.picture === \"string\" ? claims.picture : undefined,\n raw: claims,\n };\n }\n\n /** Redeem the authorization code (with the PKCE verifier) for an id_token. */\n private async exchangeCode(code: string, codeVerifier: string): Promise<string> {\n let body: { id_token?: unknown };\n\n try {\n const response = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n code,\n client_id: this.settings.clientId,\n client_secret: this.settings.clientSecret,\n redirect_uri: this.settings.redirectUri,\n grant_type: \"authorization_code\",\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Google token endpoint answered ${response.status}`);\n }\n\n body = (await response.json()) as { id_token?: unknown };\n } catch (error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", error);\n }\n\n if (typeof body.id_token !== \"string\") {\n throw new InvalidProviderCallbackError(\"missing-id-token\");\n }\n\n return body.id_token;\n }\n\n /** Verify signature, issuer, audience and expiry; resolve the claims. */\n private async verifyIdToken(jose: JoseModule, idToken: string): Promise<Record<string, unknown>> {\n googleKeySet ??= jose.createRemoteJWKSet(new URL(JWKS_URI));\n\n try {\n const { payload } = await jose.jwtVerify(idToken, googleKeySet, {\n issuer: ISSUERS,\n audience: this.settings.clientId,\n });\n\n return payload;\n } catch (error) {\n throw new InvalidProviderCallbackError(\"invalid-id-token\", error);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthProvider, LinkedInProviderConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile } from "../contracts/providers.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/providers/linkedin-provider.d.ts
|
|
4
4
|
/** The `warlock add` feature that installs `jose`. */
|
|
5
5
|
declare const LINKEDIN_AUTH_FEATURE = "auth-linkedin";
|
|
6
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkedin-provider.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"linkedin-provider.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/providers/linkedin-provider.ts"],"mappings":";;;;cAmBa,qBAAA;AAAb;;;;AAAkC;AAkBlC;;;;;;;;AAlBA,cAkBa,gBAAA,YAA4B,YAAA;EAAA,iBAGH,QAAA;EAAA,SAFpB,IAAA;cAEoB,QAAA,EAAU,sBAAA;EAEvC,gBAAA,CAAiB,KAAA,EAAO,0BAAA;EAelB,cAAA;IACX,KAAA;IACA;EAAA,GACC,sBAAA,GAAyB,OAAA,CAAQ,eAAA;EApBA;EAAA,QAsDtB,YAAA;;UAkCA,aAAA;AAAA"}
|
|
@@ -2,7 +2,7 @@ import { InvalidProviderCallbackError } from "../errors/invalid-provider-callbac
|
|
|
2
2
|
import { loadOptionalPeer } from "../services/optional-peer.mjs";
|
|
3
3
|
import { pkceChallenge } from "./google-provider.mjs";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/providers/linkedin-provider.ts
|
|
6
6
|
const AUTHORIZATION_ENDPOINT = "https://www.linkedin.com/oauth/v2/authorization";
|
|
7
7
|
const TOKEN_ENDPOINT = "https://www.linkedin.com/oauth/v2/accessToken";
|
|
8
8
|
const JWKS_URI = "https://www.linkedin.com/oauth/openid/jwks";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkedin-provider.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"linkedin-provider.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/linkedin-provider.ts"],"sourcesContent":["import type {\n AuthProvider,\n LinkedInProviderConfig,\n ProviderAuthorizationState,\n ProviderCallbackParams,\n ProviderProfile,\n} from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { loadOptionalPeer } from \"../services/optional-peer\";\nimport type { JoseModule } from \"./google-provider\";\nimport { pkceChallenge } from \"./google-provider\";\n\nconst AUTHORIZATION_ENDPOINT = \"https://www.linkedin.com/oauth/v2/authorization\";\nconst TOKEN_ENDPOINT = \"https://www.linkedin.com/oauth/v2/accessToken\";\nconst JWKS_URI = \"https://www.linkedin.com/oauth/openid/jwks\";\nconst ISSUERS = [\"https://www.linkedin.com\", \"https://www.linkedin.com/oauth\"];\nconst DEFAULT_SCOPES = [\"openid\", \"profile\", \"email\"];\n\n/** The `warlock add` feature that installs `jose`. */\nexport const LINKEDIN_AUTH_FEATURE = \"auth-linkedin\";\n\n/** LinkedIn's key set, created once per process; `jose` caches the fetched JWKS inside it. */\nlet linkedinKeySet: unknown;\n\n/**\n * LinkedIn sign-in: OpenID Connect authorization code flow with PKCE (S256)\n * and `state` and `nonce` — LinkedIn's \"Sign In with LinkedIn using OpenID\n * Connect\" product, the same shape as {@link GoogleProvider}. The code is\n * exchanged with plain `fetch`; the id_token is verified with `jose` against\n * LinkedIn's JWKS (signature, `iss`, `aud` = the client id, `exp`), then its\n * `nonce` is compared. No LinkedIn SDK.\n *\n * The exact issuer string LinkedIn signs (`ISSUERS` above) and whether its\n * token endpoint honours `code_verifier` are asserted from LinkedIn's public\n * OIDC docs, not from a live exchange — this package has no LinkedIn test\n * credentials. See the worker report for what is and is not proven.\n */\nexport class LinkedInProvider implements AuthProvider {\n public readonly name = \"linkedin\";\n\n public constructor(private readonly settings: LinkedInProviderConfig) {}\n\n public authorizationUrl(state: ProviderAuthorizationState): string {\n const url = new URL(AUTHORIZATION_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"scope\", (this.settings.scopes ?? DEFAULT_SCOPES).join(\" \"));\n url.searchParams.set(\"state\", state.state);\n url.searchParams.set(\"nonce\", state.nonce);\n url.searchParams.set(\"code_challenge\", pkceChallenge(state.codeVerifier));\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n\n return url.toString();\n }\n\n public async handleCallback({\n query,\n expected,\n }: ProviderCallbackParams): Promise<ProviderProfile> {\n // Load the peer first: a missing SDK fails loudly before any network call.\n const jose = await loadOptionalPeer<JoseModule>(\"jose\", LINKEDIN_AUTH_FEATURE);\n\n const code = query.code;\n\n if (typeof code !== \"string\" || code.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-code\");\n }\n\n const idToken = await this.exchangeCode(code, expected.codeVerifier);\n const claims = await this.verifyIdToken(jose, idToken);\n\n if (typeof claims.nonce !== \"string\" || claims.nonce !== expected.nonce) {\n throw new InvalidProviderCallbackError(\"nonce-mismatch\");\n }\n\n if (typeof claims.sub !== \"string\" || claims.sub.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-subject\");\n }\n\n return {\n provider: this.name,\n providerUserId: claims.sub,\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n // LinkedIn sends a boolean; anything else counts as NOT verified.\n emailVerified: claims.email_verified === true,\n name: typeof claims.name === \"string\" ? claims.name : undefined,\n avatar: typeof claims.picture === \"string\" ? claims.picture : undefined,\n raw: claims,\n };\n }\n\n /** Redeem the authorization code (with the PKCE verifier) for an id_token. */\n private async exchangeCode(code: string, codeVerifier: string): Promise<string> {\n let body: { id_token?: unknown };\n\n try {\n const response = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n code,\n client_id: this.settings.clientId,\n client_secret: this.settings.clientSecret,\n redirect_uri: this.settings.redirectUri,\n grant_type: \"authorization_code\",\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`LinkedIn token endpoint answered ${response.status}`);\n }\n\n body = (await response.json()) as { id_token?: unknown };\n } catch (error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", error);\n }\n\n if (typeof body.id_token !== \"string\") {\n throw new InvalidProviderCallbackError(\"missing-id-token\");\n }\n\n return body.id_token;\n }\n\n /** Verify signature, issuer, audience and expiry; resolve the claims. */\n private async verifyIdToken(jose: JoseModule, idToken: string): Promise<Record<string, unknown>> {\n linkedinKeySet ??= jose.createRemoteJWKSet(new URL(JWKS_URI));\n\n try {\n const { payload } = await jose.jwtVerify(idToken, linkedinKeySet, {\n issuer: ISSUERS,\n audience: this.settings.clientId,\n });\n\n return payload;\n } catch (error) {\n throw new InvalidProviderCallbackError(\"invalid-id-token\", error);\n }\n }\n}\n"],"mappings":";;;;;AAYA,MAAM,yBAAyB;AAC/B,MAAM,iBAAiB;AACvB,MAAM,WAAW;AACjB,MAAM,UAAU,CAAC,4BAA4B,gCAAgC;AAC7E,MAAM,iBAAiB;CAAC;CAAU;CAAW;AAAO;;AAGpD,MAAa,wBAAwB;;AAGrC,IAAI;;;;;;;;;;;;;;AAeJ,IAAa,mBAAb,MAAsD;CAGpD,AAAO,YAAY,AAAiB,UAAkC;EAAlC;cAFb;CAEgD;CAEvE,AAAO,iBAAiB,OAA2C;EACjE,MAAM,MAAM,IAAI,IAAI,sBAAsB;EAE1C,IAAI,aAAa,IAAI,aAAa,KAAK,SAAS,QAAQ;EACxD,IAAI,aAAa,IAAI,gBAAgB,KAAK,SAAS,WAAW;EAC9D,IAAI,aAAa,IAAI,iBAAiB,MAAM;EAC5C,IAAI,aAAa,IAAI,UAAU,KAAK,SAAS,UAAU,eAAc,CAAE,KAAK,GAAG,CAAC;EAChF,IAAI,aAAa,IAAI,SAAS,MAAM,KAAK;EACzC,IAAI,aAAa,IAAI,SAAS,MAAM,KAAK;EACzC,IAAI,aAAa,IAAI,kBAAkB,cAAc,MAAM,YAAY,CAAC;EACxE,IAAI,aAAa,IAAI,yBAAyB,MAAM;EAEpD,OAAO,IAAI,SAAS;CACtB;CAEA,MAAa,eAAe,EAC1B,OACA,YACmD;EAEnD,MAAM,OAAO,MAAM,iBAA6B,QAAQ,qBAAqB;EAE7E,MAAM,OAAO,MAAM;EAEnB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,6BAA6B,cAAc;EAGvD,MAAM,UAAU,MAAM,KAAK,aAAa,MAAM,SAAS,YAAY;EACnE,MAAM,SAAS,MAAM,KAAK,cAAc,MAAM,OAAO;EAErD,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,SAAS,OAChE,MAAM,IAAI,6BAA6B,gBAAgB;EAGzD,IAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,WAAW,GAC1D,MAAM,IAAI,6BAA6B,iBAAiB;EAG1D,OAAO;GACL,UAAU,KAAK;GACf,gBAAgB,OAAO;GACvB,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;GAEzD,eAAe,OAAO,mBAAmB;GACzC,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;GACtD,QAAQ,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;GAC9D,KAAK;EACP;CACF;;CAGA,MAAc,aAAa,MAAc,cAAuC;EAC9E,IAAI;EAEJ,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,gBAAgB;IAC3C,QAAQ;IACR,SAAS,EAAE,gBAAgB,oCAAoC;IAC/D,MAAM,IAAI,gBAAgB;KACxB;KACA,WAAW,KAAK,SAAS;KACzB,eAAe,KAAK,SAAS;KAC7B,cAAc,KAAK,SAAS;KAC5B,YAAY;KACZ,eAAe;IACjB,CAAC;GACH,CAAC;GAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,oCAAoC,SAAS,QAAQ;GAGvE,OAAQ,MAAM,SAAS,KAAK;EAC9B,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,wBAAwB,KAAK;EACtE;EAEA,IAAI,OAAO,KAAK,aAAa,UAC3B,MAAM,IAAI,6BAA6B,kBAAkB;EAG3D,OAAO,KAAK;CACd;;CAGA,MAAc,cAAc,MAAkB,SAAmD;EAC/F,mBAAmB,KAAK,mBAAmB,IAAI,IAAI,QAAQ,CAAC;EAE5D,IAAI;GACF,MAAM,EAAE,YAAY,MAAM,KAAK,UAAU,SAAS,gBAAgB;IAChE,QAAQ;IACR,UAAU,KAAK,SAAS;GAC1B,CAAC;GAED,OAAO;EACT,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,oBAAoB,KAAK;EAClE;CACF;AACF"}
|
|
@@ -4,7 +4,7 @@ import { DeviceInfo, LoginResult } from "../contracts/types.mjs";
|
|
|
4
4
|
import { Request, Response } from "@warlock.js/core";
|
|
5
5
|
import { ChildModel } from "@warlock.js/cascade";
|
|
6
6
|
|
|
7
|
-
//#region
|
|
7
|
+
//#region ../../packages/auth/src/providers/provider-login.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* Resolve a provider by name: a built-in name (`google`, `github`, `discord`,
|
|
10
10
|
* `linkedin`, `apple`, `facebook`, `x`) from `auth.providers.<name>`, anything
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-login.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"provider-login.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/providers/provider-login.ts"],"mappings":";;;;;;;;;AA+DA;;;iBAAgB,mBAAA,CAAoB,IAAA,WAAe,YAAY;AAAA;AA8B/D;;;;;;;;;AA9B+D,iBA8BzC,kBAAA,CACpB,QAAA,EAAU,QAAA,EACV,YAAA,WACC,OAAO;AAgCV;;;;;;;;;;;;;;;;AAAA,iBAAsB,qBAAA,WAAgC,IAAA,EACpD,KAAA,EAAO,UAAA,CAAW,CAAA,GAClB,YAAA,UACA,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,WAAA,CAAY,CAAA"}
|
|
@@ -12,7 +12,7 @@ import { XProvider } from "./x-provider.mjs";
|
|
|
12
12
|
import { randomUrlSafe, takeProviderState, writeProviderState } from "./provider-state-cookie.mjs";
|
|
13
13
|
import { resolveProviderUser } from "./resolve-provider-user.mjs";
|
|
14
14
|
|
|
15
|
-
//#region
|
|
15
|
+
//#region ../../packages/auth/src/providers/provider-login.ts
|
|
16
16
|
/** Providers auth ships, by name — each built from `auth.providers.<name>`. */
|
|
17
17
|
const BUILT_IN_PROVIDERS = {
|
|
18
18
|
google: () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-login.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"provider-login.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/provider-login.ts"],"sourcesContent":["import type { ChildModel } from \"@warlock.js/cascade\";\nimport type { Request, Response } from \"@warlock.js/core\";\nimport type { AuthProvider } from \"../contracts/providers\";\nimport type { DeviceInfo, LoginResult } from \"../contracts/types\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport type { Auth } from \"../models/auth.model\";\nimport { authConfig } from \"../services/auth-config\";\nimport { authService } from \"../services/auth.service\";\nimport { AppleProvider } from \"./apple-provider\";\nimport { DiscordProvider } from \"./discord-provider\";\nimport { FacebookProvider } from \"./facebook-provider\";\nimport { GitHubProvider } from \"./github-provider\";\nimport { GoogleProvider } from \"./google-provider\";\nimport { LinkedInProvider } from \"./linkedin-provider\";\nimport { XProvider } from \"./x-provider\";\nimport { safeEqual } from \"../utils/safe-equal\";\nimport { randomUrlSafe, takeProviderState, writeProviderState } from \"./provider-state-cookie\";\nimport { resolveProviderUser } from \"./resolve-provider-user\";\n\n/** Providers auth ships, by name — each built from `auth.providers.<name>`. */\nconst BUILT_IN_PROVIDERS: Record<string, (name: string) => AuthProvider | undefined> = {\n google: () => {\n const google = authConfig.providers.google();\n\n return google ? new GoogleProvider(google) : undefined;\n },\n github: () => {\n const github = authConfig.providers.github();\n\n return github ? new GitHubProvider(github) : undefined;\n },\n discord: () => {\n const discord = authConfig.providers.discord();\n\n return discord ? new DiscordProvider(discord) : undefined;\n },\n linkedin: () => {\n const linkedin = authConfig.providers.linkedin();\n\n return linkedin ? new LinkedInProvider(linkedin) : undefined;\n },\n apple: () => {\n const apple = authConfig.providers.apple();\n\n return apple ? new AppleProvider(apple) : undefined;\n },\n facebook: () => {\n const facebook = authConfig.providers.facebook();\n\n return facebook ? new FacebookProvider(facebook) : undefined;\n },\n x: () => {\n const x = authConfig.providers.x();\n\n return x ? new XProvider(x) : undefined;\n },\n};\n\n/**\n * Resolve a provider by name: a built-in name (`google`, `github`, `discord`,\n * `linkedin`, `apple`, `facebook`, `x`) from `auth.providers.<name>`, anything\n * else from `auth.providers.custom`. Throws naming the config key when absent.\n */\nexport function resolveAuthProvider(name: string): AuthProvider {\n const isBuiltIn = Object.hasOwn(BUILT_IN_PROVIDERS, name);\n\n if (isBuiltIn) {\n const provider = BUILT_IN_PROVIDERS[name]!(name);\n\n if (provider) return provider;\n } else {\n const custom = authConfig.providers.custom(name);\n\n if (custom) return custom;\n }\n\n throw new Error(\n `@warlock.js/auth: no login provider \"${name}\" — configure \\`auth.providers.${\n isBuiltIn ? name : `custom.${name}`\n }\\`.`,\n );\n}\n\n/**\n * Start a redirect login: generate `state`, `nonce` and a PKCE verifier, store\n * them in a signed 10-minute cookie, and resolve the provider URL to redirect\n * the browser to.\n *\n * @example\n * // GET /auth/google\n * export const googleRedirect: RequestHandler = async (_request, response) =>\n * response.redirect(await startProviderLogin(response, \"google\"));\n */\nexport async function startProviderLogin(\n response: Response,\n providerName: string,\n): Promise<string> {\n const provider = resolveAuthProvider(providerName);\n\n const state = {\n state: randomUrlSafe(),\n nonce: randomUrlSafe(),\n codeVerifier: randomUrlSafe(),\n };\n\n const url = await provider.authorizationUrl(state);\n\n writeProviderState(response, provider.name, state, provider.callbackMode ?? \"query\");\n\n return url;\n}\n\n/**\n * Finish a redirect login on the provider's callback: consume the state\n * cookie, reject a missing/forged/expired cookie, a cookie from another\n * provider, a provider-reported error or a `state` mismatch; let the provider\n * verify the callback; find-or-link the user; then log them in through\n * `authService.completeLogin` — the same outcome as password login.\n *\n * @throws InvalidProviderCallbackError (400, EC009) for any callback rejection.\n * @throws ProviderEmailNotVerifiedError (403, EC010) for an unlinked, unverified email.\n * @throws AuthProviderSdkMissingError when the provider's SDK is not installed.\n *\n * @example\n * // GET /auth/google/callback\n * const { user, tokens } = await completeProviderLogin(User, \"google\", request, response);\n * authService.setAuthCookie(response, tokens.accessToken);\n */\nexport async function completeProviderLogin<T extends Auth>(\n Model: ChildModel<T>,\n providerName: string,\n request: Request,\n response: Response,\n deviceInfo?: DeviceInfo,\n): Promise<LoginResult<T>> {\n const provider = resolveAuthProvider(providerName);\n const expected = takeProviderState(request, response);\n\n if (!expected) {\n throw new InvalidProviderCallbackError(\"missing-or-invalid-state-cookie\");\n }\n\n if (expected.provider !== provider.name) {\n throw new InvalidProviderCallbackError(\"state-cookie-for-another-provider\");\n }\n\n const query: Record<string, unknown> = {\n code: request.input(\"code\"),\n state: request.input(\"state\"),\n error: request.input(\"error\"),\n // Apple's form_post callback carries the account name here, on the first authorization only.\n user: request.input(\"user\"),\n };\n\n if (query.error) {\n throw new InvalidProviderCallbackError(`provider-error:${String(query.error)}`);\n }\n\n if (typeof query.state !== \"string\" || !safeEqual(query.state, expected.state)) {\n throw new InvalidProviderCallbackError(\"state-mismatch\");\n }\n\n const profile = await provider.handleCallback({\n query,\n expected: { state: expected.state, nonce: expected.nonce, codeVerifier: expected.codeVerifier },\n });\n\n const user = await resolveProviderUser(Model, profile);\n\n return authService.completeLogin(user, deviceInfo);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,qBAAiF;CACrF,cAAc;EACZ,MAAM,SAAS,WAAW,UAAU,OAAO;EAE3C,OAAO,SAAS,IAAI,eAAe,MAAM,IAAI;CAC/C;CACA,cAAc;EACZ,MAAM,SAAS,WAAW,UAAU,OAAO;EAE3C,OAAO,SAAS,IAAI,eAAe,MAAM,IAAI;CAC/C;CACA,eAAe;EACb,MAAM,UAAU,WAAW,UAAU,QAAQ;EAE7C,OAAO,UAAU,IAAI,gBAAgB,OAAO,IAAI;CAClD;CACA,gBAAgB;EACd,MAAM,WAAW,WAAW,UAAU,SAAS;EAE/C,OAAO,WAAW,IAAI,iBAAiB,QAAQ,IAAI;CACrD;CACA,aAAa;EACX,MAAM,QAAQ,WAAW,UAAU,MAAM;EAEzC,OAAO,QAAQ,IAAI,cAAc,KAAK,IAAI;CAC5C;CACA,gBAAgB;EACd,MAAM,WAAW,WAAW,UAAU,SAAS;EAE/C,OAAO,WAAW,IAAI,iBAAiB,QAAQ,IAAI;CACrD;CACA,SAAS;EACP,MAAM,IAAI,WAAW,UAAU,EAAE;EAEjC,OAAO,IAAI,IAAI,UAAU,CAAC,IAAI;CAChC;AACF;;;;;;AAOA,SAAgB,oBAAoB,MAA4B;CAC9D,MAAM,YAAY,OAAO,OAAO,oBAAoB,IAAI;CAExD,IAAI,WAAW;EACb,MAAM,WAAW,mBAAmB,KAAK,CAAE,IAAI;EAE/C,IAAI,UAAU,OAAO;CACvB,OAAO;EACL,MAAM,SAAS,WAAW,UAAU,OAAO,IAAI;EAE/C,IAAI,QAAQ,OAAO;CACrB;CAEA,MAAM,IAAI,MACR,wCAAwC,KAAK,iCAC3C,YAAY,OAAO,UAAU,OAC9B,IACH;AACF;;;;;;;;;;;AAYA,eAAsB,mBACpB,UACA,cACiB;CACjB,MAAM,WAAW,oBAAoB,YAAY;CAEjD,MAAM,QAAQ;EACZ,OAAO,cAAc;EACrB,OAAO,cAAc;EACrB,cAAc,cAAc;CAC9B;CAEA,MAAM,MAAM,MAAM,SAAS,iBAAiB,KAAK;CAEjD,mBAAmB,UAAU,SAAS,MAAM,OAAO,SAAS,gBAAgB,OAAO;CAEnF,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,eAAsB,sBACpB,OACA,cACA,SACA,UACA,YACyB;CACzB,MAAM,WAAW,oBAAoB,YAAY;CACjD,MAAM,WAAW,kBAAkB,SAAS,QAAQ;CAEpD,IAAI,CAAC,UACH,MAAM,IAAI,6BAA6B,iCAAiC;CAG1E,IAAI,SAAS,aAAa,SAAS,MACjC,MAAM,IAAI,6BAA6B,mCAAmC;CAG5E,MAAM,QAAiC;EACrC,MAAM,QAAQ,MAAM,MAAM;EAC1B,OAAO,QAAQ,MAAM,OAAO;EAC5B,OAAO,QAAQ,MAAM,OAAO;EAE5B,MAAM,QAAQ,MAAM,MAAM;CAC5B;CAEA,IAAI,MAAM,OACR,MAAM,IAAI,6BAA6B,kBAAkB,OAAO,MAAM,KAAK,GAAG;CAGhF,IAAI,OAAO,MAAM,UAAU,YAAY,CAAC,UAAU,MAAM,OAAO,SAAS,KAAK,GAC3E,MAAM,IAAI,6BAA6B,gBAAgB;CAQzD,MAAM,OAAO,MAAM,oBAAoB,OAAO,MALxB,SAAS,eAAe;EAC5C;EACA,UAAU;GAAE,OAAO,SAAS;GAAO,OAAO,SAAS;GAAO,cAAc,SAAS;EAAa;CAChG,CAAC,CAEoD;CAErD,OAAO,YAAY,cAAc,MAAM,UAAU;AACnD"}
|
|
@@ -2,7 +2,7 @@ import { authConfig } from "../services/auth-config.mjs";
|
|
|
2
2
|
import { safeEqual } from "../utils/safe-equal.mjs";
|
|
3
3
|
import { createHmac, randomBytes } from "node:crypto";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/providers/provider-state-cookie.ts
|
|
6
6
|
/** Cookie carrying a redirect login's state from the start to the callback. */
|
|
7
7
|
const PROVIDER_STATE_COOKIE = "auth_provider_state";
|
|
8
8
|
/** How long a started provider login stays completable. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-state-cookie.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"provider-state-cookie.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/provider-state-cookie.ts"],"sourcesContent":["import type { Request, Response } from \"@warlock.js/core\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport type { ProviderAuthorizationState } from \"../contracts/providers\";\nimport { authConfig } from \"../services/auth-config\";\nimport { safeEqual } from \"../utils/safe-equal\";\n\n/** Cookie carrying a redirect login's state from the start to the callback. */\nexport const PROVIDER_STATE_COOKIE = \"auth_provider_state\";\n\n/** How long a started provider login stays completable. */\nconst PROVIDER_STATE_TTL_SECONDS = 600;\n\n/** What the signed cookie holds. */\nexport type StoredProviderState = ProviderAuthorizationState & {\n provider: string;\n expiresAt: number;\n callbackMode: \"query\" | \"form_post\";\n};\n\n/** 32 random bytes, base64url — used for `state`, `nonce` and the PKCE verifier. */\nexport function randomUrlSafe(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * HMAC over the payload with a key DERIVED from the access-token secret, so\n * the state cookie never shares a MAC key with the JWTs themselves.\n */\nfunction sign(payload: string): string {\n const key = createHmac(\"sha256\", authConfig.accessToken.secret())\n .update(\"@warlock.js/auth:provider-state\")\n .digest();\n\n return createHmac(\"sha256\", key).update(payload).digest(\"base64url\");\n}\n\n/**\n * Write the signed, 10-minute state cookie for a provider login. `HttpOnly`\n * comes from the `response.cookie()` secure defaults. `callbackMode` picks\n * the `SameSite`/`Secure` pair: `\"query\"` (default) keeps the framework's\n * `SameSite=Lax`, which still rides a top-level GET redirect back to the app;\n * `\"form_post\"` (Apple) overrides to `SameSite=None; Secure`, since a browser\n * drops a `Lax` cookie on the provider's cross-site POST callback. Either\n * way, the signed `state`/`nonce`/PKCE values — not the cookie's SameSite\n * attribute — are what actually defend against CSRF here.\n */\nexport function writeProviderState(\n response: Response,\n provider: string,\n state: ProviderAuthorizationState,\n callbackMode: \"query\" | \"form_post\" = \"query\",\n): void {\n const stored: StoredProviderState = {\n ...state,\n provider,\n expiresAt: Date.now() + PROVIDER_STATE_TTL_SECONDS * 1000,\n callbackMode,\n };\n const payload = Buffer.from(JSON.stringify(stored)).toString(\"base64url\");\n\n response.cookie(PROVIDER_STATE_COOKIE, `${payload}.${sign(payload)}`, {\n raw: true,\n path: \"/\",\n maxAge: PROVIDER_STATE_TTL_SECONDS,\n ...(callbackMode === \"form_post\" ? { sameSite: \"none\", secure: true } : {}),\n });\n}\n\n/**\n * Read AND clear the state cookie, so a started login completes at most once.\n * Resolves `undefined` for an absent, forged (bad MAC), malformed or expired\n * cookie. The clearing `Set-Cookie` repeats whatever `SameSite`/`Secure` pair\n * the cookie was WRITTEN with (recovered from the decoded payload itself, so\n * this needs no mode passed in) — a browser only honours a clearing cookie\n * when it matches the original's attributes, and for a `form_post` (Apple)\n * cookie that means `SameSite=None; Secure` too, or the original survives.\n */\nexport function takeProviderState(\n request: Request,\n response: Response,\n): StoredProviderState | undefined {\n const raw: unknown = request.cookie(PROVIDER_STATE_COOKIE);\n\n let stored: Partial<StoredProviderState> | undefined;\n\n if (typeof raw === \"string\") {\n const [payload, signature, ...rest] = raw.split(\".\");\n\n if (payload && signature && rest.length === 0 && safeEqual(signature, sign(payload))) {\n try {\n stored = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf8\"));\n } catch {\n stored = undefined;\n }\n }\n }\n\n response.clearCookie(PROVIDER_STATE_COOKIE, {\n path: \"/\",\n ...(stored?.callbackMode === \"form_post\" ? { sameSite: \"none\", secure: true } : {}),\n });\n\n if (\n typeof stored?.state !== \"string\" ||\n typeof stored.nonce !== \"string\" ||\n typeof stored.codeVerifier !== \"string\" ||\n typeof stored.provider !== \"string\" ||\n typeof stored.expiresAt !== \"number\" ||\n stored.expiresAt <= Date.now()\n ) {\n return undefined;\n }\n\n return stored as StoredProviderState;\n}\n"],"mappings":";;;;;;AAOA,MAAa,wBAAwB;;AAGrC,MAAM,6BAA6B;;AAUnC,SAAgB,gBAAwB;CACtC,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,WAAW;AAC7C;;;;;AAMA,SAAS,KAAK,SAAyB;CAKrC,OAAO,WAAW,UAJN,WAAW,UAAU,WAAW,YAAY,OAAO,CAAC,CAAC,CAC9D,OAAO,iCAAiC,CAAC,CACzC,OAE2B,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAAO,WAAW;AACrE;;;;;;;;;;;AAYA,SAAgB,mBACd,UACA,UACA,OACA,eAAsC,SAChC;CACN,MAAM,SAA8B;EAClC,GAAG;EACH;EACA,WAAW,KAAK,IAAI,IAAI,6BAA6B;EACrD;CACF;CACA,MAAM,UAAU,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW;CAExE,SAAS,OAAO,uBAAuB,GAAG,QAAQ,GAAG,KAAK,OAAO,KAAK;EACpE,KAAK;EACL,MAAM;EACN,QAAQ;EACR,GAAI,iBAAiB,cAAc;GAAE,UAAU;GAAQ,QAAQ;EAAK,IAAI,CAAC;CAC3E,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,kBACd,SACA,UACiC;CACjC,MAAM,MAAe,QAAQ,OAAO,qBAAqB;CAEzD,IAAI;CAEJ,IAAI,OAAO,QAAQ,UAAU;EAC3B,MAAM,CAAC,SAAS,WAAW,GAAG,QAAQ,IAAI,MAAM,GAAG;EAEnD,IAAI,WAAW,aAAa,KAAK,WAAW,KAAK,UAAU,WAAW,KAAK,OAAO,CAAC,GACjF,IAAI;GACF,SAAS,KAAK,MAAM,OAAO,KAAK,SAAS,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EACxE,QAAQ;GACN,SAAS;EACX;CAEJ;CAEA,SAAS,YAAY,uBAAuB;EAC1C,MAAM;EACN,GAAI,QAAQ,iBAAiB,cAAc;GAAE,UAAU;GAAQ,QAAQ;EAAK,IAAI,CAAC;CACnF,CAAC;CAED,IACE,OAAO,QAAQ,UAAU,YACzB,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,iBAAiB,YAC/B,OAAO,OAAO,aAAa,YAC3B,OAAO,OAAO,cAAc,YAC5B,OAAO,aAAa,KAAK,IAAI,GAE7B;CAGF,OAAO;AACT"}
|
|
@@ -4,7 +4,7 @@ import { ProviderEmailNotVerifiedError } from "../errors/provider-email-not-veri
|
|
|
4
4
|
import { ProviderAccount } from "../models/provider-account/provider-account.model.mjs";
|
|
5
5
|
import "../models/provider-account/index.mjs";
|
|
6
6
|
|
|
7
|
-
//#region
|
|
7
|
+
//#region ../../packages/auth/src/providers/resolve-provider-user.ts
|
|
8
8
|
/** Default creation for a first provider login: email, name, and the verified stamp. */
|
|
9
9
|
async function createUserFor(Model, profile) {
|
|
10
10
|
const createUser = authConfig.providers.createUser();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-provider-user.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"resolve-provider-user.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/resolve-provider-user.ts"],"sourcesContent":["import type { ChildModel } from \"@warlock.js/cascade\";\nimport type { ProviderProfile } from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { ProviderEmailNotVerifiedError } from \"../errors/provider-email-not-verified.error\";\nimport type { Auth } from \"../models/auth.model\";\nimport { ProviderAccount } from \"../models/provider-account\";\nimport { authConfig } from \"../services/auth-config\";\n\n/** Default creation for a first provider login: email, name, and the verified stamp. */\nasync function createUserFor<T extends Auth>(\n Model: ChildModel<T>,\n profile: ProviderProfile,\n): Promise<T> {\n const createUser = authConfig.providers.createUser();\n\n if (createUser) {\n return (await createUser(profile, Model as unknown as ChildModel<Auth>)) as T;\n }\n\n return (await Model.create({\n [authConfig.providers.emailField()]: profile.email,\n ...(profile.name ? { name: profile.name } : {}),\n // The provider verified this address; record it the way verifyEmail does.\n [authConfig.verification.field()]: new Date(),\n } as never)) as T;\n}\n\n/**\n * Find or create the app user for a provider profile, and link them.\n *\n * 1. An existing `provider_accounts` link wins — the provider's stable id, not\n * the email, identifies the account from then on.\n * 2. Otherwise the provider email MUST be verified. An unverified (or absent)\n * email throws {@link ProviderEmailNotVerifiedError} and nothing is linked\n * or created: linking on it would let anyone who can add an unverified\n * address at the provider take over the matching account.\n * 3. Verified: the user whose `auth.providers.emailField` matches, else a new\n * user (`auth.providers.createUser`, or the default) — then the link is stored.\n */\nexport async function resolveProviderUser<T extends Auth>(\n Model: ChildModel<T>,\n profile: ProviderProfile,\n): Promise<T> {\n const link = await ProviderAccount.findLink(profile.provider, profile.providerUserId);\n\n if (link) {\n const linked = (await Model.find(link.userId)) as T | null;\n\n if (!linked || linked.userType !== link.userType) {\n throw new InvalidProviderCallbackError(\"link-points-to-missing-user\");\n }\n\n return linked;\n }\n\n if (!profile.emailVerified || !profile.email) {\n throw new ProviderEmailNotVerifiedError();\n }\n\n const existing = (await Model.first({\n [authConfig.providers.emailField()]: profile.email,\n })) as T | null;\n\n const user = existing ?? (await createUserFor(Model, profile));\n\n await ProviderAccount.link(profile.provider, profile.providerUserId, user, profile.email);\n\n return user;\n}\n"],"mappings":";;;;;;;;AASA,eAAe,cACb,OACA,SACY;CACZ,MAAM,aAAa,WAAW,UAAU,WAAW;CAEnD,IAAI,YACF,OAAQ,MAAM,WAAW,SAAS,KAAoC;CAGxE,OAAQ,MAAM,MAAM,OAAO;GACxB,WAAW,UAAU,WAAW,IAAI,QAAQ;EAC7C,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;GAE5C,WAAW,aAAa,MAAM,oBAAI,IAAI,KAAK;CAC9C,CAAU;AACZ;;;;;;;;;;;;;AAcA,eAAsB,oBACpB,OACA,SACY;CACZ,MAAM,OAAO,MAAM,gBAAgB,SAAS,QAAQ,UAAU,QAAQ,cAAc;CAEpF,IAAI,MAAM;EACR,MAAM,SAAU,MAAM,MAAM,KAAK,KAAK,MAAM;EAE5C,IAAI,CAAC,UAAU,OAAO,aAAa,KAAK,UACtC,MAAM,IAAI,6BAA6B,6BAA6B;EAGtE,OAAO;CACT;CAEA,IAAI,CAAC,QAAQ,iBAAiB,CAAC,QAAQ,OACrC,MAAM,IAAI,8BAA8B;CAO1C,MAAM,OAAO,MAJW,MAAM,MAAM,GACjC,WAAW,UAAU,WAAW,IAAI,QAAQ,MAC/C,CAAC,KAEyB,MAAM,cAAc,OAAO,OAAO;CAE5D,MAAM,gBAAgB,KAAK,QAAQ,UAAU,QAAQ,gBAAgB,MAAM,QAAQ,KAAK;CAExF,OAAO;AACT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthProvider, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, XProviderConfig } from "../contracts/providers.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/auth/src/providers/x-provider.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* X (Twitter) sign-in: OAuth 2.0 authorization code flow with PKCE (S256). X
|
|
6
6
|
* is not OpenID Connect — there is no `id_token`. X's confidential clients
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-provider.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"x-provider.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/providers/x-provider.ts"],"mappings":";;;;;AAoCA;;;;;;;;;;;;cAAa,SAAA,YAAqB,YAAA;EAAA,iBAGI,QAAA;EAAA,SAFpB,IAAA;cAEoB,QAAA,EAAU,eAAA;EAEvC,gBAAA,CAAiB,KAAA,EAAO,0BAAA;EAclB,cAAA;IACX,KAAA;IACA;EAAA,GACC,sBAAA,GAAyB,OAAA,CAAQ,eAAA;EAnBA;EAAA,QA8C5B,SAAA;EA5CuB;EAAA,QAmDjB,YAAA;EAAA,QAuCA,SAAA;AAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InvalidProviderCallbackError } from "../errors/invalid-provider-callback.error.mjs";
|
|
2
2
|
import { pkceChallenge } from "./google-provider.mjs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/auth/src/providers/x-provider.ts
|
|
5
5
|
const AUTHORIZATION_ENDPOINT = "https://twitter.com/i/oauth2/authorize";
|
|
6
6
|
const TOKEN_ENDPOINT = "https://api.twitter.com/2/oauth2/token";
|
|
7
7
|
const USER_ENDPOINT = "https://api.twitter.com/2/users/me";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-provider.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"x-provider.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/providers/x-provider.ts"],"sourcesContent":["import type {\n AuthProvider,\n ProviderAuthorizationState,\n ProviderCallbackParams,\n ProviderProfile,\n XProviderConfig,\n} from \"../contracts/providers\";\nimport { InvalidProviderCallbackError } from \"../errors/invalid-provider-callback.error\";\nimport { pkceChallenge } from \"./google-provider\";\n\nconst AUTHORIZATION_ENDPOINT = \"https://twitter.com/i/oauth2/authorize\";\nconst TOKEN_ENDPOINT = \"https://api.twitter.com/2/oauth2/token\";\nconst USER_ENDPOINT = \"https://api.twitter.com/2/users/me\";\nconst DEFAULT_SCOPES = [\"tweet.read\", \"users.read\"];\n\ntype XUser = {\n id: string;\n name?: string;\n username?: string;\n profile_image_url?: string;\n};\n\n/**\n * X (Twitter) sign-in: OAuth 2.0 authorization code flow with PKCE (S256). X\n * is not OpenID Connect — there is no `id_token`. X's confidential clients\n * authenticate the token request with HTTP Basic auth (`client_id:client_secret`),\n * on top of PKCE — both are required together, per X's developer docs (this\n * package has no X test app, so the exchange itself is unverified against a\n * live endpoint). The access token is then redeemed once against\n * `GET /2/users/me`.\n *\n * X does not return an email address from this API at all — there is no\n * \"email\" scope to request it with — so `email` is always left `undefined`\n * rather than invented, and `resolveProviderUser` rejects it the same way it\n * rejects any other missing email.\n */\nexport class XProvider implements AuthProvider {\n public readonly name = \"x\";\n\n public constructor(private readonly settings: XProviderConfig) {}\n\n public authorizationUrl(state: ProviderAuthorizationState): string {\n const url = new URL(AUTHORIZATION_ENDPOINT);\n\n url.searchParams.set(\"client_id\", this.settings.clientId);\n url.searchParams.set(\"redirect_uri\", this.settings.redirectUri);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"scope\", (this.settings.scopes ?? DEFAULT_SCOPES).join(\" \"));\n url.searchParams.set(\"state\", state.state);\n url.searchParams.set(\"code_challenge\", pkceChallenge(state.codeVerifier));\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n\n return url.toString();\n }\n\n public async handleCallback({\n query,\n expected,\n }: ProviderCallbackParams): Promise<ProviderProfile> {\n const code = query.code;\n\n if (typeof code !== \"string\" || code.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-code\");\n }\n\n const accessToken = await this.exchangeCode(code, expected.codeVerifier);\n const user = await this.fetchUser(accessToken);\n\n if (typeof user.id !== \"string\" || user.id.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-subject\");\n }\n\n return {\n provider: this.name,\n providerUserId: user.id,\n // X does not hand back an email address from this API.\n email: undefined,\n emailVerified: false,\n name: user.name ?? user.username,\n avatar: user.profile_image_url,\n raw: user,\n };\n }\n\n /** Basic-auth credential for the confidential-client token request. */\n private basicAuth(): string {\n return Buffer.from(`${this.settings.clientId}:${this.settings.clientSecret}`).toString(\n \"base64\",\n );\n }\n\n /** Redeem the authorization code (with the PKCE verifier) for an access token. */\n private async exchangeCode(code: string, codeVerifier: string): Promise<string> {\n let body: { access_token?: unknown; error?: unknown };\n\n try {\n const response = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n authorization: `Basic ${this.basicAuth()}`,\n },\n body: new URLSearchParams({\n code,\n client_id: this.settings.clientId,\n redirect_uri: this.settings.redirectUri,\n grant_type: \"authorization_code\",\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`X token endpoint answered ${response.status}`);\n }\n\n body = (await response.json()) as { access_token?: unknown; error?: unknown };\n } catch (error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", error);\n }\n\n if (body.error) {\n throw new InvalidProviderCallbackError(\"code-exchange-failed\", body.error);\n }\n\n if (typeof body.access_token !== \"string\" || body.access_token.length === 0) {\n throw new InvalidProviderCallbackError(\"missing-access-token\");\n }\n\n return body.access_token;\n }\n\n private async fetchUser(accessToken: string): Promise<XUser> {\n try {\n const url = new URL(USER_ENDPOINT);\n\n url.searchParams.set(\"user.fields\", \"profile_image_url\");\n\n const response = await fetch(url.toString(), {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n\n if (!response.ok) {\n throw new Error(`X /2/users/me endpoint answered ${response.status}`);\n }\n\n const body = (await response.json()) as { data?: XUser };\n\n return body.data ?? ({} as XUser);\n } catch (error) {\n throw new InvalidProviderCallbackError(\"profile-fetch-failed\", error);\n }\n }\n}\n"],"mappings":";;;;AAUA,MAAM,yBAAyB;AAC/B,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AACtB,MAAM,iBAAiB,CAAC,cAAc,YAAY;;;;;;;;;;;;;;;AAuBlD,IAAa,YAAb,MAA+C;CAG7C,AAAO,YAAY,AAAiB,UAA2B;EAA3B;cAFb;CAEyC;CAEhE,AAAO,iBAAiB,OAA2C;EACjE,MAAM,MAAM,IAAI,IAAI,sBAAsB;EAE1C,IAAI,aAAa,IAAI,aAAa,KAAK,SAAS,QAAQ;EACxD,IAAI,aAAa,IAAI,gBAAgB,KAAK,SAAS,WAAW;EAC9D,IAAI,aAAa,IAAI,iBAAiB,MAAM;EAC5C,IAAI,aAAa,IAAI,UAAU,KAAK,SAAS,UAAU,eAAc,CAAE,KAAK,GAAG,CAAC;EAChF,IAAI,aAAa,IAAI,SAAS,MAAM,KAAK;EACzC,IAAI,aAAa,IAAI,kBAAkB,cAAc,MAAM,YAAY,CAAC;EACxE,IAAI,aAAa,IAAI,yBAAyB,MAAM;EAEpD,OAAO,IAAI,SAAS;CACtB;CAEA,MAAa,eAAe,EAC1B,OACA,YACmD;EACnD,MAAM,OAAO,MAAM;EAEnB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,6BAA6B,cAAc;EAGvD,MAAM,cAAc,MAAM,KAAK,aAAa,MAAM,SAAS,YAAY;EACvE,MAAM,OAAO,MAAM,KAAK,UAAU,WAAW;EAE7C,IAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,WAAW,GACpD,MAAM,IAAI,6BAA6B,iBAAiB;EAG1D,OAAO;GACL,UAAU,KAAK;GACf,gBAAgB,KAAK;GAErB,OAAO;GACP,eAAe;GACf,MAAM,KAAK,QAAQ,KAAK;GACxB,QAAQ,KAAK;GACb,KAAK;EACP;CACF;;CAGA,AAAQ,YAAoB;EAC1B,OAAO,OAAO,KAAK,GAAG,KAAK,SAAS,SAAS,GAAG,KAAK,SAAS,cAAc,CAAC,CAAC,SAC5E,QACF;CACF;;CAGA,MAAc,aAAa,MAAc,cAAuC;EAC9E,IAAI;EAEJ,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,gBAAgB;IAC3C,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,eAAe,SAAS,KAAK,UAAU;IACzC;IACA,MAAM,IAAI,gBAAgB;KACxB;KACA,WAAW,KAAK,SAAS;KACzB,cAAc,KAAK,SAAS;KAC5B,YAAY;KACZ,eAAe;IACjB,CAAC;GACH,CAAC;GAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,6BAA6B,SAAS,QAAQ;GAGhE,OAAQ,MAAM,SAAS,KAAK;EAC9B,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,wBAAwB,KAAK;EACtE;EAEA,IAAI,KAAK,OACP,MAAM,IAAI,6BAA6B,wBAAwB,KAAK,KAAK;EAG3E,IAAI,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,WAAW,GACxE,MAAM,IAAI,6BAA6B,sBAAsB;EAG/D,OAAO,KAAK;CACd;CAEA,MAAc,UAAU,aAAqC;EAC3D,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,aAAa;GAEjC,IAAI,aAAa,IAAI,eAAe,mBAAmB;GAEvD,MAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG,EAC3C,SAAS,EAAE,eAAe,UAAU,cAAc,EACpD,CAAC;GAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,mCAAmC,SAAS,QAAQ;GAKtE,QAAO,MAFa,SAAS,KAAK,EAEvB,CAAC,QAAS,CAAC;EACxB,SAAS,OAAO;GACd,MAAM,IAAI,6BAA6B,wBAAwB,KAAK;EACtE;CACF;AACF"}
|
|
@@ -2,7 +2,7 @@ import { config } from "@warlock.js/core";
|
|
|
2
2
|
import { log } from "@warlock.js/logger";
|
|
3
3
|
import ms from "ms";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/auth/src/services/auth-config.ts
|
|
6
6
|
const warnedLegacyKeys = /* @__PURE__ */ new Set();
|
|
7
7
|
const permitAuthentication = () => true;
|
|
8
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-config.mjs","names":[],"sources":["../../../../../../../auth/src/services/auth-config.ts"],"sourcesContent":["import { config } from \"@warlock.js/core\";\nimport type { CookieDomainResolver } from \"../contracts/types\";\nimport { log } from \"@warlock.js/logger\";\nimport { type Algorithm } from \"fast-jwt\";\nimport ms from \"ms\";\nimport type {\n AuthNotification,\n CanAuthenticate,\n LogoutWithoutTokenBehavior,\n OneTimeTokenUrlBuilder,\n PasswordSetter,\n} from \"../contracts/types\";\nimport type {\n AppleProviderConfig,\n AuthProvider,\n DiscordProviderConfig,\n FacebookProviderConfig,\n GitHubProviderConfig,\n GoogleProviderConfig,\n LinkedInProviderConfig,\n OtpSender,\n PasskeysConfig,\n ProviderUserCreator,\n XProviderConfig,\n} from \"../contracts/providers\";\nimport type { Auth } from \"../models/auth.model\";\n\nconst warnedLegacyKeys = new Set<string>();\n\nconst permitAuthentication: CanAuthenticate = (): boolean => true;\n\n/**\n * `ms` accepts anything at runtime and answers `undefined` for a string it\n * cannot parse (`\"30dayz\"`), a *formatted string* for a number (`2592000` ⇒\n * `\"43m\"`), and throws for `\"\"`. Its published types claim the far narrower\n * `(value: ms.StringValue) => number`, which is exactly what let an\n * `as ms.StringValue` cast smuggle arbitrary config text past the compiler and\n * hand `undefined` to the JWT signer. Declare the honest runtime signature once,\n * here, so every caller is forced to deal with the `undefined`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * Turn a configured lifetime into a positive number of milliseconds, or throw\n * naming the key.\n *\n * A token lifetime is a security boundary, so an unusable value is a hard error\n * rather than a silent fallback: substituting a default would trade one\n * unintended lifetime for another, just as quietly — and quiet is the whole\n * problem. Every non-positive, non-finite or non-numeric parse is rejected:\n *\n * - `\"30dayz\"` / `\"thirty days\"` ⇒ `undefined` ⇒ a JWT signed with **no `exp`\n * claim** and an `Invalid Date` written to the token row.\n * - `\"0d\"` ⇒ parses cleanly to `0`, which `fast-jwt` treats as falsy and also\n * emits with **no `exp` claim** — it survives any guard that only rejects\n * `undefined`. `\"-1h\"` signs an already-expired token.\n * - `2592000` (a bare number) ⇒ `ms` *formats* it as `\"43m\"`, a string, which\n * then turns `Date.now() + expiresIn` into an `Invalid Date`.\n */\nfunction parseDuration(key: string, raw: unknown): number {\n let parsed: number | string | undefined;\n\n try {\n parsed = parseMs(raw);\n } catch {\n // ms throws (rather than returning undefined) for \"\" and non-strings\n parsed = undefined;\n }\n\n if (typeof parsed !== \"number\" || !Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(\n `auth.${key}: ${JSON.stringify(raw)} is not a valid ms duration — ` +\n `use a positive duration string such as \"1h\", \"7d\", or NO_EXPIRATION.`,\n );\n }\n\n return parsed;\n}\n\n/** Email-verification token lifetime used when nothing is configured. */\nconst DEFAULT_VERIFICATION_EXPIRES_IN = \"24h\";\n\n/** Password-reset token lifetime used when nothing is configured. */\nconst DEFAULT_PASSWORD_RESET_EXPIRES_IN = \"60m\";\n\n/** Passkey challenge lifetime used when nothing is configured. */\nconst DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN = \"5m\";\n\n/** OTP lifetime used when nothing is configured. */\nconst DEFAULT_OTP_EXPIRES_IN = \"5m\";\n\n/** Access-token lifetime used when nothing is configured. */\nconst DEFAULT_ACCESS_TOKEN_EXPIRES_IN = \"1h\";\n\n/**\n * Resolve an auth setting, preferring the new `auth.accessToken.*` /\n * `auth.refreshToken.*` key and falling back to the deprecated `auth.jwt.*`\n * shape — warning once per legacy key. Returns `fallback` when neither is set.\n *\n * This is the backward-compatible shim that lets existing `auth.jwt.*` configs\n * keep working after the config split.\n */\nfunction resolve<T>(newKey: string, legacyKey: string, fallback?: T): T {\n const fromNew = config.key(`auth.${newKey}`);\n\n if (fromNew !== undefined && fromNew !== null) {\n return fromNew as T;\n }\n\n const fromLegacy = config.key(`auth.${legacyKey}`);\n\n if (fromLegacy !== undefined && fromLegacy !== null) {\n if (!warnedLegacyKeys.has(legacyKey)) {\n warnedLegacyKeys.add(legacyKey);\n log.warn(\n \"auth\",\n \"config-deprecation\",\n `auth.${legacyKey} is deprecated — use auth.${newKey}`,\n );\n }\n\n return fromLegacy as T;\n }\n\n return fallback as T;\n}\n\n/**\n * Typed, backward-compatible access to auth configuration. The service and the\n * jwt signer read configuration exclusively through here, so the new split\n * config and the legacy `auth.jwt.*` shape both resolve the same way.\n */\nexport const authConfig = {\n canAuthenticate: async (user: Auth): Promise<boolean> => {\n const canAuthenticate = config.key<CanAuthenticate>(\n \"auth.canAuthenticate\",\n permitAuthentication,\n );\n\n return canAuthenticate(user);\n },\n accessToken: {\n /** Signing secret (legacy: `auth.jwt.secret`). Throws if neither is set. */\n secret: (): string => {\n const secret = resolve<string | undefined>(\"accessToken.secret\", \"jwt.secret\");\n\n if (!secret) {\n throw new Error(\"auth: no JWT secret configured — set `auth.accessToken.secret`.\");\n }\n\n return secret;\n },\n /** Signing algorithm (legacy: `auth.jwt.algorithm`). */\n algorithm: (): Algorithm => resolve(\"accessToken.algorithm\", \"jwt.algorithm\", \"HS256\"),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.expiresIn`). */\n expiresIn: (): string | undefined => resolve(\"accessToken.expiresIn\", \"jwt.expiresIn\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Defaults\n * to 1 hour when unset; throws naming the key when the configured value is\n * not a positive `ms` duration. See {@link parseDuration}.\n */\n expiresInMs: (): number =>\n parseDuration(\n \"accessToken.expiresIn\",\n authConfig.accessToken.expiresIn() ?? DEFAULT_ACCESS_TOKEN_EXPIRES_IN,\n ),\n },\n pageAuth: {\n /**\n * Login path a guarded PAGE route redirects a logged-out browser to.\n *\n * Unset (the default) ⇒ page-route auth failures keep the JSON 401 API\n * contract, so existing apps see NO behavior change. Set it (e.g. \"/login\")\n * to opt a browser-facing app into login-redirect UX: a logged-out human\n * navigating to a guarded page lands on the login screen instead of reading\n * a raw JSON error blob. Finding b9ab9804.\n */\n loginPath: (): string | undefined => config.key(\"auth.pageAuth.loginPath\"),\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed. Defaults to\n * `\"returnUrl\"`.\n */\n returnUrlParam: (): string => config.key(\"auth.pageAuth.returnUrlParam\", \"returnUrl\"),\n },\n cookie: {\n /** Cookie name `setAuthCookie`/`clearAuthCookie` use. @default \"access_token\" */\n name: (): string => config.key(\"auth.cookie.name\", \"access_token\"),\n /** Refresh-token cookie name the session cookie helpers use. @default \"refresh_token\" */\n refreshName: (): string => config.key(\"auth.cookie.refreshName\", \"refresh_token\"),\n /** Cookie `Path` attribute `setAuthCookie`/`clearAuthCookie` use. @default \"/\" */\n path: (): string => config.key(\"auth.cookie.path\", \"/\"),\n /** Select the request-scoped cookie Domain; undefined keeps it host-only. */\n cookieDomain: (): CookieDomainResolver | undefined => config.key(\"auth.cookie.cookieDomain\"),\n },\n csrf: {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * beyond the request's own origin. @default []\n */\n allowedOrigins: (): string[] => config.key(\"auth.csrf.allowedOrigins\", []),\n },\n verification: {\n /** Lifetime in ms of an email-verification token; throws naming the key when unusable. @default \"24h\" */\n expiresInMs: (): number =>\n parseDuration(\n \"verification.expiresIn\",\n config.key(\"auth.verification.expiresIn\", DEFAULT_VERIFICATION_EXPIRES_IN),\n ),\n /** User attribute stamped with the verification date. @default \"emailVerifiedAt\" */\n field: (): string => config.key(\"auth.verification.field\", \"emailVerifiedAt\"),\n /** App notification replacing the default verification email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.verification.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.verification.url\"),\n },\n passwordReset: {\n /** Lifetime in ms of a password-reset token; throws naming the key when unusable. @default \"60m\" */\n expiresInMs: (): number =>\n parseDuration(\n \"passwordReset.expiresIn\",\n config.key(\"auth.passwordReset.expiresIn\", DEFAULT_PASSWORD_RESET_EXPIRES_IN),\n ),\n /** User attribute `requestPasswordReset` looks the account up by. @default \"email\" */\n identifierField: (): string => config.key(\"auth.passwordReset.identifierField\", \"email\"),\n /** App notification replacing the default reset email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.passwordReset.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.passwordReset.url\"),\n /** App-owned password writer replacing the default. */\n setPassword: (): PasswordSetter | undefined => config.key(\"auth.passwordReset.setPassword\"),\n },\n providers: {\n /** `auth.providers.google`, when configured. */\n google: (): GoogleProviderConfig | undefined => config.key(\"auth.providers.google\"),\n /** `auth.providers.github`, when configured. */\n github: (): GitHubProviderConfig | undefined => config.key(\"auth.providers.github\"),\n /** `auth.providers.discord`, when configured. */\n discord: (): DiscordProviderConfig | undefined => config.key(\"auth.providers.discord\"),\n /** `auth.providers.linkedin`, when configured. */\n linkedin: (): LinkedInProviderConfig | undefined => config.key(\"auth.providers.linkedin\"),\n /** `auth.providers.apple`, when configured. */\n apple: (): AppleProviderConfig | undefined => config.key(\"auth.providers.apple\"),\n /** `auth.providers.facebook`, when configured. */\n facebook: (): FacebookProviderConfig | undefined => config.key(\"auth.providers.facebook\"),\n /** `auth.providers.x`, when configured. */\n x: (): XProviderConfig | undefined => config.key(\"auth.providers.x\"),\n /** An app-registered provider under `auth.providers.custom.<name>`. */\n custom: (name: string): AuthProvider | undefined => {\n const providers = config.key<Record<string, AuthProvider>>(\"auth.providers.custom\", {});\n\n // Own keys only: `name` arrives from a URL, and \"constructor\" must not resolve to Object.\n return Object.hasOwn(providers, name) ? providers[name] : undefined;\n },\n /** User attribute a verified provider email is matched against. @default \"email\" */\n emailField: (): string => config.key(\"auth.providers.emailField\", \"email\"),\n /** App-owned user creation for a first provider login. */\n createUser: (): ProviderUserCreator | undefined => config.key(\"auth.providers.createUser\"),\n },\n passkeys: {\n /** `auth.passkeys`; throws naming the keys when `rpID`, `rpName` or `origin` is missing. */\n settings: (): PasskeysConfig => {\n const settings = config.key<PasskeysConfig | undefined>(\"auth.passkeys\");\n\n if (!settings?.rpID || !settings.rpName || !settings.origin) {\n throw new Error(\n \"@warlock.js/auth: passkeys need `auth.passkeys.rpID`, `auth.passkeys.rpName` and `auth.passkeys.origin`.\",\n );\n }\n\n return settings;\n },\n /** Allowed ceremony origins, as a list. */\n origins: (): string[] => {\n const { origin } = authConfig.passkeys.settings();\n\n return Array.isArray(origin) ? origin : [origin];\n },\n /** Challenge lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n challengeExpiresInMs: (): number =>\n parseDuration(\n \"passkeys.challengeExpiresIn\",\n config.key(\"auth.passkeys.challengeExpiresIn\", DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN),\n ),\n },\n otp: {\n /** Notifications channel the code is sent through. @default \"sms\" */\n channel: (): string => config.key(\"auth.otp.channel\", \"sms\"),\n /** User attribute holding the phone number. @default \"phone\" */\n phoneField: (): string => config.key(\"auth.otp.phoneField\", \"phone\"),\n /** Code lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n expiresInMs: (): number =>\n parseDuration(\"otp.expiresIn\", config.key(\"auth.otp.expiresIn\", DEFAULT_OTP_EXPIRES_IN)),\n /** Verify attempts before the code is invalidated. @default 5 */\n maxAttempts: (): number => config.key(\"auth.otp.maxAttempts\", 5),\n /** The message text for a code. */\n message: (code: string): string => {\n const build = config.key<((code: string) => string) | undefined>(\"auth.otp.message\");\n\n return build ? build(code) : `Your verification code is ${code}`;\n },\n /** App-owned delivery replacing the notifications channel send. */\n send: (): OtpSender | undefined => config.key(\"auth.otp.send\"),\n },\n refreshToken: {\n /** Separate refresh secret (legacy: `auth.jwt.refresh.secret`); empty ⇒ fall back to the access secret. */\n secret: (): string | undefined => resolve(\"refreshToken.secret\", \"jwt.refresh.secret\"),\n /** Whether refresh tokens are enabled (legacy: `auth.jwt.refresh.enabled`). */\n enabled: (): boolean => resolve(\"refreshToken.enabled\", \"jwt.refresh.enabled\", true),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.refresh.expiresIn`). */\n expiresIn: (): string => resolve(\"refreshToken.expiresIn\", \"jwt.refresh.expiresIn\", \"7d\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Throws\n * naming the key when the configured value is not a positive `ms` duration.\n */\n expiresInMs: (): number =>\n parseDuration(\"refreshToken.expiresIn\", authConfig.refreshToken.expiresIn()),\n /** Rotate-on-use (legacy: `auth.jwt.refresh.rotation`). */\n rotation: (): boolean => resolve(\"refreshToken.rotation\", \"jwt.refresh.rotation\", true),\n /** Max active tokens per user (legacy: `auth.jwt.refresh.maxPerUser`). */\n maxPerUser: (): number => resolve(\"refreshToken.maxPerUser\", \"jwt.refresh.maxPerUser\", 5),\n /** Logout-without-token behavior (legacy: `auth.jwt.refresh.logoutWithoutToken`). */\n logoutWithoutToken: (): LogoutWithoutTokenBehavior =>\n resolve(\"refreshToken.logoutWithoutToken\", \"jwt.refresh.logoutWithoutToken\", \"revoke-all\"),\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth-config.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/services/auth-config.ts"],"sourcesContent":["import { config } from \"@warlock.js/core\";\nimport type { CookieDomainResolver } from \"../contracts/types\";\nimport { log } from \"@warlock.js/logger\";\nimport { type Algorithm } from \"fast-jwt\";\nimport ms from \"ms\";\nimport type {\n AuthNotification,\n CanAuthenticate,\n LogoutWithoutTokenBehavior,\n OneTimeTokenUrlBuilder,\n PasswordSetter,\n} from \"../contracts/types\";\nimport type {\n AppleProviderConfig,\n AuthProvider,\n DiscordProviderConfig,\n FacebookProviderConfig,\n GitHubProviderConfig,\n GoogleProviderConfig,\n LinkedInProviderConfig,\n OtpSender,\n PasskeysConfig,\n ProviderUserCreator,\n XProviderConfig,\n} from \"../contracts/providers\";\nimport type { Auth } from \"../models/auth.model\";\n\nconst warnedLegacyKeys = new Set<string>();\n\nconst permitAuthentication: CanAuthenticate = (): boolean => true;\n\n/**\n * `ms` accepts anything at runtime and answers `undefined` for a string it\n * cannot parse (`\"30dayz\"`), a *formatted string* for a number (`2592000` ⇒\n * `\"43m\"`), and throws for `\"\"`. Its published types claim the far narrower\n * `(value: ms.StringValue) => number`, which is exactly what let an\n * `as ms.StringValue` cast smuggle arbitrary config text past the compiler and\n * hand `undefined` to the JWT signer. Declare the honest runtime signature once,\n * here, so every caller is forced to deal with the `undefined`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * Turn a configured lifetime into a positive number of milliseconds, or throw\n * naming the key.\n *\n * A token lifetime is a security boundary, so an unusable value is a hard error\n * rather than a silent fallback: substituting a default would trade one\n * unintended lifetime for another, just as quietly — and quiet is the whole\n * problem. Every non-positive, non-finite or non-numeric parse is rejected:\n *\n * - `\"30dayz\"` / `\"thirty days\"` ⇒ `undefined` ⇒ a JWT signed with **no `exp`\n * claim** and an `Invalid Date` written to the token row.\n * - `\"0d\"` ⇒ parses cleanly to `0`, which `fast-jwt` treats as falsy and also\n * emits with **no `exp` claim** — it survives any guard that only rejects\n * `undefined`. `\"-1h\"` signs an already-expired token.\n * - `2592000` (a bare number) ⇒ `ms` *formats* it as `\"43m\"`, a string, which\n * then turns `Date.now() + expiresIn` into an `Invalid Date`.\n */\nfunction parseDuration(key: string, raw: unknown): number {\n let parsed: number | string | undefined;\n\n try {\n parsed = parseMs(raw);\n } catch {\n // ms throws (rather than returning undefined) for \"\" and non-strings\n parsed = undefined;\n }\n\n if (typeof parsed !== \"number\" || !Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(\n `auth.${key}: ${JSON.stringify(raw)} is not a valid ms duration — ` +\n `use a positive duration string such as \"1h\", \"7d\", or NO_EXPIRATION.`,\n );\n }\n\n return parsed;\n}\n\n/** Email-verification token lifetime used when nothing is configured. */\nconst DEFAULT_VERIFICATION_EXPIRES_IN = \"24h\";\n\n/** Password-reset token lifetime used when nothing is configured. */\nconst DEFAULT_PASSWORD_RESET_EXPIRES_IN = \"60m\";\n\n/** Passkey challenge lifetime used when nothing is configured. */\nconst DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN = \"5m\";\n\n/** OTP lifetime used when nothing is configured. */\nconst DEFAULT_OTP_EXPIRES_IN = \"5m\";\n\n/** Access-token lifetime used when nothing is configured. */\nconst DEFAULT_ACCESS_TOKEN_EXPIRES_IN = \"1h\";\n\n/**\n * Resolve an auth setting, preferring the new `auth.accessToken.*` /\n * `auth.refreshToken.*` key and falling back to the deprecated `auth.jwt.*`\n * shape — warning once per legacy key. Returns `fallback` when neither is set.\n *\n * This is the backward-compatible shim that lets existing `auth.jwt.*` configs\n * keep working after the config split.\n */\nfunction resolve<T>(newKey: string, legacyKey: string, fallback?: T): T {\n const fromNew = config.key(`auth.${newKey}`);\n\n if (fromNew !== undefined && fromNew !== null) {\n return fromNew as T;\n }\n\n const fromLegacy = config.key(`auth.${legacyKey}`);\n\n if (fromLegacy !== undefined && fromLegacy !== null) {\n if (!warnedLegacyKeys.has(legacyKey)) {\n warnedLegacyKeys.add(legacyKey);\n log.warn(\n \"auth\",\n \"config-deprecation\",\n `auth.${legacyKey} is deprecated — use auth.${newKey}`,\n );\n }\n\n return fromLegacy as T;\n }\n\n return fallback as T;\n}\n\n/**\n * Typed, backward-compatible access to auth configuration. The service and the\n * jwt signer read configuration exclusively through here, so the new split\n * config and the legacy `auth.jwt.*` shape both resolve the same way.\n */\nexport const authConfig = {\n canAuthenticate: async (user: Auth): Promise<boolean> => {\n const canAuthenticate = config.key<CanAuthenticate>(\n \"auth.canAuthenticate\",\n permitAuthentication,\n );\n\n return canAuthenticate(user);\n },\n accessToken: {\n /** Signing secret (legacy: `auth.jwt.secret`). Throws if neither is set. */\n secret: (): string => {\n const secret = resolve<string | undefined>(\"accessToken.secret\", \"jwt.secret\");\n\n if (!secret) {\n throw new Error(\"auth: no JWT secret configured — set `auth.accessToken.secret`.\");\n }\n\n return secret;\n },\n /** Signing algorithm (legacy: `auth.jwt.algorithm`). */\n algorithm: (): Algorithm => resolve(\"accessToken.algorithm\", \"jwt.algorithm\", \"HS256\"),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.expiresIn`). */\n expiresIn: (): string | undefined => resolve(\"accessToken.expiresIn\", \"jwt.expiresIn\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Defaults\n * to 1 hour when unset; throws naming the key when the configured value is\n * not a positive `ms` duration. See {@link parseDuration}.\n */\n expiresInMs: (): number =>\n parseDuration(\n \"accessToken.expiresIn\",\n authConfig.accessToken.expiresIn() ?? DEFAULT_ACCESS_TOKEN_EXPIRES_IN,\n ),\n },\n pageAuth: {\n /**\n * Login path a guarded PAGE route redirects a logged-out browser to.\n *\n * Unset (the default) ⇒ page-route auth failures keep the JSON 401 API\n * contract, so existing apps see NO behavior change. Set it (e.g. \"/login\")\n * to opt a browser-facing app into login-redirect UX: a logged-out human\n * navigating to a guarded page lands on the login screen instead of reading\n * a raw JSON error blob. Finding b9ab9804.\n */\n loginPath: (): string | undefined => config.key(\"auth.pageAuth.loginPath\"),\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed. Defaults to\n * `\"returnUrl\"`.\n */\n returnUrlParam: (): string => config.key(\"auth.pageAuth.returnUrlParam\", \"returnUrl\"),\n },\n cookie: {\n /** Cookie name `setAuthCookie`/`clearAuthCookie` use. @default \"access_token\" */\n name: (): string => config.key(\"auth.cookie.name\", \"access_token\"),\n /** Refresh-token cookie name the session cookie helpers use. @default \"refresh_token\" */\n refreshName: (): string => config.key(\"auth.cookie.refreshName\", \"refresh_token\"),\n /** Cookie `Path` attribute `setAuthCookie`/`clearAuthCookie` use. @default \"/\" */\n path: (): string => config.key(\"auth.cookie.path\", \"/\"),\n /** Select the request-scoped cookie Domain; undefined keeps it host-only. */\n cookieDomain: (): CookieDomainResolver | undefined => config.key(\"auth.cookie.cookieDomain\"),\n },\n csrf: {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * beyond the request's own origin. @default []\n */\n allowedOrigins: (): string[] => config.key(\"auth.csrf.allowedOrigins\", []),\n },\n verification: {\n /** Lifetime in ms of an email-verification token; throws naming the key when unusable. @default \"24h\" */\n expiresInMs: (): number =>\n parseDuration(\n \"verification.expiresIn\",\n config.key(\"auth.verification.expiresIn\", DEFAULT_VERIFICATION_EXPIRES_IN),\n ),\n /** User attribute stamped with the verification date. @default \"emailVerifiedAt\" */\n field: (): string => config.key(\"auth.verification.field\", \"emailVerifiedAt\"),\n /** App notification replacing the default verification email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.verification.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.verification.url\"),\n },\n passwordReset: {\n /** Lifetime in ms of a password-reset token; throws naming the key when unusable. @default \"60m\" */\n expiresInMs: (): number =>\n parseDuration(\n \"passwordReset.expiresIn\",\n config.key(\"auth.passwordReset.expiresIn\", DEFAULT_PASSWORD_RESET_EXPIRES_IN),\n ),\n /** User attribute `requestPasswordReset` looks the account up by. @default \"email\" */\n identifierField: (): string => config.key(\"auth.passwordReset.identifierField\", \"email\"),\n /** App notification replacing the default reset email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.passwordReset.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.passwordReset.url\"),\n /** App-owned password writer replacing the default. */\n setPassword: (): PasswordSetter | undefined => config.key(\"auth.passwordReset.setPassword\"),\n },\n providers: {\n /** `auth.providers.google`, when configured. */\n google: (): GoogleProviderConfig | undefined => config.key(\"auth.providers.google\"),\n /** `auth.providers.github`, when configured. */\n github: (): GitHubProviderConfig | undefined => config.key(\"auth.providers.github\"),\n /** `auth.providers.discord`, when configured. */\n discord: (): DiscordProviderConfig | undefined => config.key(\"auth.providers.discord\"),\n /** `auth.providers.linkedin`, when configured. */\n linkedin: (): LinkedInProviderConfig | undefined => config.key(\"auth.providers.linkedin\"),\n /** `auth.providers.apple`, when configured. */\n apple: (): AppleProviderConfig | undefined => config.key(\"auth.providers.apple\"),\n /** `auth.providers.facebook`, when configured. */\n facebook: (): FacebookProviderConfig | undefined => config.key(\"auth.providers.facebook\"),\n /** `auth.providers.x`, when configured. */\n x: (): XProviderConfig | undefined => config.key(\"auth.providers.x\"),\n /** An app-registered provider under `auth.providers.custom.<name>`. */\n custom: (name: string): AuthProvider | undefined => {\n const providers = config.key<Record<string, AuthProvider>>(\"auth.providers.custom\", {});\n\n // Own keys only: `name` arrives from a URL, and \"constructor\" must not resolve to Object.\n return Object.hasOwn(providers, name) ? providers[name] : undefined;\n },\n /** User attribute a verified provider email is matched against. @default \"email\" */\n emailField: (): string => config.key(\"auth.providers.emailField\", \"email\"),\n /** App-owned user creation for a first provider login. */\n createUser: (): ProviderUserCreator | undefined => config.key(\"auth.providers.createUser\"),\n },\n passkeys: {\n /** `auth.passkeys`; throws naming the keys when `rpID`, `rpName` or `origin` is missing. */\n settings: (): PasskeysConfig => {\n const settings = config.key<PasskeysConfig | undefined>(\"auth.passkeys\");\n\n if (!settings?.rpID || !settings.rpName || !settings.origin) {\n throw new Error(\n \"@warlock.js/auth: passkeys need `auth.passkeys.rpID`, `auth.passkeys.rpName` and `auth.passkeys.origin`.\",\n );\n }\n\n return settings;\n },\n /** Allowed ceremony origins, as a list. */\n origins: (): string[] => {\n const { origin } = authConfig.passkeys.settings();\n\n return Array.isArray(origin) ? origin : [origin];\n },\n /** Challenge lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n challengeExpiresInMs: (): number =>\n parseDuration(\n \"passkeys.challengeExpiresIn\",\n config.key(\"auth.passkeys.challengeExpiresIn\", DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN),\n ),\n },\n otp: {\n /** Notifications channel the code is sent through. @default \"sms\" */\n channel: (): string => config.key(\"auth.otp.channel\", \"sms\"),\n /** User attribute holding the phone number. @default \"phone\" */\n phoneField: (): string => config.key(\"auth.otp.phoneField\", \"phone\"),\n /** Code lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n expiresInMs: (): number =>\n parseDuration(\"otp.expiresIn\", config.key(\"auth.otp.expiresIn\", DEFAULT_OTP_EXPIRES_IN)),\n /** Verify attempts before the code is invalidated. @default 5 */\n maxAttempts: (): number => config.key(\"auth.otp.maxAttempts\", 5),\n /** The message text for a code. */\n message: (code: string): string => {\n const build = config.key<((code: string) => string) | undefined>(\"auth.otp.message\");\n\n return build ? build(code) : `Your verification code is ${code}`;\n },\n /** App-owned delivery replacing the notifications channel send. */\n send: (): OtpSender | undefined => config.key(\"auth.otp.send\"),\n },\n refreshToken: {\n /** Separate refresh secret (legacy: `auth.jwt.refresh.secret`); empty ⇒ fall back to the access secret. */\n secret: (): string | undefined => resolve(\"refreshToken.secret\", \"jwt.refresh.secret\"),\n /** Whether refresh tokens are enabled (legacy: `auth.jwt.refresh.enabled`). */\n enabled: (): boolean => resolve(\"refreshToken.enabled\", \"jwt.refresh.enabled\", true),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.refresh.expiresIn`). */\n expiresIn: (): string => resolve(\"refreshToken.expiresIn\", \"jwt.refresh.expiresIn\", \"7d\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Throws\n * naming the key when the configured value is not a positive `ms` duration.\n */\n expiresInMs: (): number =>\n parseDuration(\"refreshToken.expiresIn\", authConfig.refreshToken.expiresIn()),\n /** Rotate-on-use (legacy: `auth.jwt.refresh.rotation`). */\n rotation: (): boolean => resolve(\"refreshToken.rotation\", \"jwt.refresh.rotation\", true),\n /** Max active tokens per user (legacy: `auth.jwt.refresh.maxPerUser`). */\n maxPerUser: (): number => resolve(\"refreshToken.maxPerUser\", \"jwt.refresh.maxPerUser\", 5),\n /** Logout-without-token behavior (legacy: `auth.jwt.refresh.logoutWithoutToken`). */\n logoutWithoutToken: (): LogoutWithoutTokenBehavior =>\n resolve(\"refreshToken.logoutWithoutToken\", \"jwt.refresh.logoutWithoutToken\", \"revoke-all\"),\n },\n};\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { DeviceInfo, TokenPair } from "../contracts/types.mjs";
|
|
|
3
3
|
import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
|
|
4
4
|
import { EventSubscription } from "@mongez/events";
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../../packages/auth/src/services/auth-events.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Auth event payload types
|
|
9
9
|
*/
|