@shware/security 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -99
- package/dist/auth/index.cjs +44 -36
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +6 -6
- package/dist/auth/index.d.cts.map +1 -1
- package/dist/auth/index.d.mts +6 -6
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +44 -36
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/types.d.cts +12 -7
- package/dist/auth/types.d.cts.map +1 -1
- package/dist/auth/types.d.mts +12 -7
- package/dist/auth/types.d.mts.map +1 -1
- package/dist/index.cjs +4 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/oauth2/client.cjs +113 -49
- package/dist/oauth2/client.cjs.map +1 -1
- package/dist/oauth2/client.d.cts +54 -14
- package/dist/oauth2/client.d.cts.map +1 -1
- package/dist/oauth2/client.d.mts +54 -14
- package/dist/oauth2/client.d.mts.map +1 -1
- package/dist/oauth2/client.mjs +113 -49
- package/dist/oauth2/client.mjs.map +1 -1
- package/dist/oauth2/name.cjs +107 -0
- package/dist/oauth2/name.cjs.map +1 -0
- package/dist/oauth2/name.d.cts +49 -0
- package/dist/oauth2/name.d.cts.map +1 -0
- package/dist/oauth2/name.d.mts +49 -0
- package/dist/oauth2/name.d.mts.map +1 -0
- package/dist/oauth2/name.mjs +101 -0
- package/dist/oauth2/name.mjs.map +1 -0
- package/dist/oauth2/provider/apple-client-secret.cjs +3 -3
- package/dist/oauth2/provider/apple-client-secret.cjs.map +1 -1
- package/dist/oauth2/provider/apple-client-secret.d.cts +3 -3
- package/dist/oauth2/provider/apple-client-secret.d.mts +3 -3
- package/dist/oauth2/provider/apple-client-secret.mjs +3 -3
- package/dist/oauth2/provider/apple-client-secret.mjs.map +1 -1
- package/dist/oauth2/provider/apple.cjs +38 -10
- package/dist/oauth2/provider/apple.cjs.map +1 -1
- package/dist/oauth2/provider/apple.d.cts +19 -14
- package/dist/oauth2/provider/apple.d.cts.map +1 -1
- package/dist/oauth2/provider/apple.d.mts +19 -14
- package/dist/oauth2/provider/apple.d.mts.map +1 -1
- package/dist/oauth2/provider/apple.mjs +38 -10
- package/dist/oauth2/provider/apple.mjs.map +1 -1
- package/dist/oauth2/provider/common.cjs +13 -9
- package/dist/oauth2/provider/common.cjs.map +1 -1
- package/dist/oauth2/provider/common.d.cts +13 -2
- package/dist/oauth2/provider/common.d.cts.map +1 -1
- package/dist/oauth2/provider/common.d.mts +13 -2
- package/dist/oauth2/provider/common.d.mts.map +1 -1
- package/dist/oauth2/provider/common.mjs +13 -9
- package/dist/oauth2/provider/common.mjs.map +1 -1
- package/dist/oauth2/provider/facebook.cjs +59 -35
- package/dist/oauth2/provider/facebook.cjs.map +1 -1
- package/dist/oauth2/provider/facebook.d.cts.map +1 -1
- package/dist/oauth2/provider/facebook.d.mts.map +1 -1
- package/dist/oauth2/provider/facebook.mjs +59 -35
- package/dist/oauth2/provider/facebook.mjs.map +1 -1
- package/dist/oauth2/provider/google.cjs +31 -32
- package/dist/oauth2/provider/google.cjs.map +1 -1
- package/dist/oauth2/provider/google.d.cts.map +1 -1
- package/dist/oauth2/provider/google.d.mts.map +1 -1
- package/dist/oauth2/provider/google.mjs +31 -32
- package/dist/oauth2/provider/google.mjs.map +1 -1
- package/dist/oauth2/provider/index.cjs +1 -1
- package/dist/oauth2/provider/index.mjs +1 -1
- package/dist/oauth2/provider/wechat.cjs +1 -0
- package/dist/oauth2/provider/wechat.cjs.map +1 -1
- package/dist/oauth2/provider/wechat.d.cts.map +1 -1
- package/dist/oauth2/provider/wechat.d.mts.map +1 -1
- package/dist/oauth2/provider/wechat.mjs +1 -0
- package/dist/oauth2/provider/wechat.mjs.map +1 -1
- package/dist/oauth2/types.cjs.map +1 -1
- package/dist/oauth2/types.d.cts +100 -33
- package/dist/oauth2/types.d.cts.map +1 -1
- package/dist/oauth2/types.d.mts +100 -33
- package/dist/oauth2/types.d.mts.map +1 -1
- package/dist/oauth2/types.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["object","optional","string"],"sources":["../../src/oauth2/client.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { object, optional, string } from 'zod/v4-mini';\nimport type {\n NativeCredential,\n OAuth2ClientConfig,\n OAuth2Token,\n OidcToken,\n PkceParameters,\n} from './types';\n\nexport const oauth2RedirectQuerySchema = object({\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n});\n\nexport const googleOneTapSchema = object({\n // login_uri\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n nonce: optional(string()),\n g_csrf_token: optional(string()),\n hd: optional(string()),\n // callback\n credential: optional(string()),\n});\n\nexport class OAuth2Client {\n private readonly config: OAuth2ClientConfig;\n constructor(config: OAuth2ClientConfig) {\n this.config = config;\n }\n\n get baseUri() {\n return this.config.baseUri;\n }\n\n get successUri() {\n return this.config.successUri;\n }\n\n get errorUri() {\n return this.config.errorUri;\n }\n\n private async getClientConfig(registrationId: string) {\n const registrations =\n typeof this.config.registration === 'function'\n ? await this.config.registration()\n : this.config.registration;\n\n const registration = registrations[registrationId];\n invariant(registration, `Registration ${registrationId} not found`);\n\n const provider = this.config.provider[registration.provider ?? registrationId];\n invariant(provider, `Provider ${registration.provider ?? registrationId} not found`);\n return { registration, provider };\n }\n\n async createAuthorizationUri({\n registrationId,\n state,\n pkce,\n }: {\n registrationId: string;\n state: string;\n pkce?: PkceParameters;\n }): Promise<URL> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n\n const { baseUri } = this.config;\n const { scope, clientId, redirectUri } = registration;\n return provider.createAuthorizationUri({\n pkce,\n state,\n scope,\n clientId,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n });\n }\n\n async exchangeAuthorizationCode({\n registrationId,\n code,\n pkce,\n }: {\n registrationId: string;\n code: string;\n pkce?: PkceParameters;\n }): Promise<OAuth2Token> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n\n const { baseUri } = this.config;\n const { clientId, clientSecret, redirectUri } = registration;\n return provider.exchangeAuthorizationCode({\n code,\n pkce,\n clientId,\n clientSecret,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n });\n }\n\n async getUserInfo({\n registrationId,\n token,\n }: {\n registrationId: string;\n token: OAuth2Token | OidcToken;\n }) {\n const { provider } = await this.getClientConfig(registrationId);\n return provider.getUserInfo(token);\n }\n\n async refreshAccessToken({\n registrationId,\n refreshToken,\n }: {\n registrationId: string;\n refreshToken: string;\n }): Promise<OAuth2Token> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n const { clientId, clientSecret } = registration;\n\n invariant(provider.refreshAccessToken, 'Provider does not support refreshAccessToken');\n return provider.refreshAccessToken({ refreshToken, clientId, clientSecret });\n }\n\n async revokeToken(registrationId: string, token: string) {\n const { provider, registration } = await this.getClientConfig(registrationId);\n const { clientId, clientSecret } = registration;\n\n invariant(provider.revokeToken, 'Provider does not support revokeToken');\n await provider.revokeToken({ token, clientId, clientSecret });\n }\n\n async loginOAuth2Native({\n registrationId,\n credentials,\n pkce,\n }: {\n registrationId: string;\n credentials: NativeCredential;\n pkce?: PkceParameters;\n }) {\n const { provider, registration } = await this.getClientConfig(registrationId);\n invariant(provider.loginOAuth2Native, 'Provider does not support loginOAuth2Native');\n const { baseUri } = this.config;\n const { clientId, clientSecret, redirectUri } = registration;\n\n return provider.loginOAuth2Native({\n clientId,\n clientSecret,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n credentials,\n pkce,\n });\n }\n}\n"],"mappings":";;;;AAUA,MAAa,6BAAA,GAA4BA,YAAAA,OAAAA,CAAO;CAC9C,OAAA,GAAMC,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACvB,QAAA,GAAOD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACxB,QAAA,GAAOD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACxB,oBAAA,GAAmBD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACpC,YAAA,GAAWD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;AAC9B,CAAC;AAED,MAAa,sBAAA,GAAqBF,YAAAA,OAAAA,CAAO;CAEvC,OAAA,GAAMC,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACvB,QAAA,GAAOD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACxB,QAAA,GAAOD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACxB,oBAAA,GAAmBD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACpC,YAAA,GAAWD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CAC5B,QAAA,GAAOD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CACxB,eAAA,GAAcD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CAC/B,KAAA,GAAID,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;CAErB,aAAA,GAAYD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;AAC/B,CAAC;AAED,IAAa,eAAb,MAA0B;CACxB;CACA,YAAY,QAA4B;EACtC,KAAK,SAAS;CAChB;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,aAAa;EACf,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,WAAW;EACb,OAAO,KAAK,OAAO;CACrB;CAEA,MAAc,gBAAgB,gBAAwB;EAMpD,MAAM,gBAJJ,OAAO,KAAK,OAAO,iBAAiB,aAChC,MAAM,KAAK,OAAO,aAAa,IAC/B,KAAK,OAAO,aAAA,CAEiB;EACnC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,gBAAgB,eAAe,WAAW;EAElE,MAAM,WAAW,KAAK,OAAO,SAAS,aAAa,YAAY;EAC/D,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,YAAY,aAAa,YAAY,eAAe,WAAW;EACnF,OAAO;GAAE;GAAc;EAAS;CAClC;CAEA,MAAM,uBAAuB,EAC3B,gBACA,OACA,QAKe;EACf,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAE5E,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,OAAO,UAAU,gBAAgB;EACzC,OAAO,SAAS,uBAAuB;GACrC;GACA;GACA;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;EAC9D,CAAC;CACH;CAEA,MAAM,0BAA0B,EAC9B,gBACA,MACA,QAKuB;EACvB,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAE5E,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,UAAU,cAAc,gBAAgB;EAChD,OAAO,SAAS,0BAA0B;GACxC;GACA;GACA;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;EAC9D,CAAC;CACH;CAEA,MAAM,YAAY,EAChB,gBACA,SAIC;EACD,MAAM,EAAE,aAAa,MAAM,KAAK,gBAAgB,cAAc;EAC9D,OAAO,SAAS,YAAY,KAAK;CACnC;CAEA,MAAM,mBAAmB,EACvB,gBACA,gBAIuB;EACvB,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,MAAM,EAAE,UAAU,iBAAiB;EAEnC,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,oBAAoB,8CAA8C;EACrF,OAAO,SAAS,mBAAmB;GAAE;GAAc;GAAU;EAAa,CAAC;CAC7E;CAEA,MAAM,YAAY,gBAAwB,OAAe;EACvD,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,MAAM,EAAE,UAAU,iBAAiB;EAEnC,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,aAAa,uCAAuC;EACvE,MAAM,SAAS,YAAY;GAAE;GAAO;GAAU;EAAa,CAAC;CAC9D;CAEA,MAAM,kBAAkB,EACtB,gBACA,aACA,QAKC;EACD,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,CAAA,GAAA,cAAA,UAAA,CAAU,SAAS,mBAAmB,6CAA6C;EACnF,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,UAAU,cAAc,gBAAgB;EAEhD,OAAO,SAAS,kBAAkB;GAChC;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;GAC5D;GACA;EACF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["object","optional","string","OAuth2Error"],"sources":["../../src/oauth2/client.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { object, optional, string } from 'zod/v4-mini';\nimport { OAuth2Error } from './error';\nimport type {\n AuthorizationResponse,\n ClientSecret,\n NativeClient,\n NativeCredential,\n OAuth2ClientConfig,\n OAuth2Token,\n OidcToken,\n OneTapProvider,\n PkceParameters,\n Provider,\n ProviderConfig,\n WebClient,\n} from './types';\n\nexport const oauth2RedirectQuerySchema = object({\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n // Apple's form_post: the user's name as JSON, first authorization only.\n user: optional(string()),\n});\n\nexport const googleOneTapSchema = object({\n // login_uri\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n nonce: optional(string()),\n g_csrf_token: optional(string()),\n hd: optional(string()),\n // callback\n credential: optional(string()),\n});\n\nexport class OAuth2Client {\n private readonly config: OAuth2ClientConfig;\n constructor(config: OAuth2ClientConfig) {\n this.config = config;\n }\n\n get baseUri() {\n return this.config.baseUri;\n }\n\n get successUri() {\n return this.config.successUri;\n }\n\n get errorUri() {\n return this.config.errorUri;\n }\n\n /** The provider key comes from the request path, so an unknown one is the caller's error. */\n private async getProviderConfig(provider: string): Promise<ProviderConfig> {\n const providers =\n typeof this.config.providers === 'function'\n ? await this.config.providers()\n : this.config.providers;\n const config = Object.hasOwn(providers, provider) ? providers[provider] : undefined;\n if (!config) throw new OAuth2Error(400, 'invalid_request', `Unknown provider: ${provider}`);\n return config;\n }\n\n private defaultRedirectUri(provider: string) {\n return `${this.config.baseUri}/login/oauth2/code/${provider}`;\n }\n\n /** The redirect flow and One Tap sign in as the provider's web client. */\n private async getWebClient(provider: string) {\n const config = await this.getProviderConfig(provider);\n if (!config.web) {\n throw new OAuth2Error(400, 'invalid_request', `Provider ${provider} has no web client`);\n }\n return { implementation: config.provider, client: config.web };\n }\n\n /**\n * Any configured client of the provider, looked up by id: refresh and revoke use the client a\n * token was issued to, and the native login the one the app names. The web client counts too:\n * an Android app's server auth code and a Facebook app's tokens are issued to it.\n */\n private async getClient(provider: string, clientId: string) {\n const config = await this.getProviderConfig(provider);\n const client = [config.web, ...(config.native ?? [])].find((c) => c?.clientId === clientId);\n if (!client) {\n throw new OAuth2Error(400, 'invalid_client', `${clientId} is not a client of ${provider}`);\n }\n return { implementation: config.provider, client };\n }\n\n /**\n * The client a native login signs in as: the one the app names with `client_id`, which must be\n * configured; without one, the only native client, or the web client when there is no native\n * one. Several native clients and no `client_id` is ambiguous and refused rather than guessed.\n */\n private async getNativeClient(provider: string, clientId: string | undefined) {\n if (clientId !== undefined) return this.getClient(provider, clientId);\n const config = await this.getProviderConfig(provider);\n const native = config.native ?? [];\n const client: WebClient | NativeClient | undefined =\n native.length === 0 ? config.web : native.length === 1 ? native[0] : undefined;\n if (!client) {\n throw new OAuth2Error(400, 'invalid_request', `client_id is required for ${provider}`);\n }\n return { implementation: config.provider, client };\n }\n\n private async resolveSecret(secret: ClientSecret | undefined, clientId: string) {\n return typeof secret === 'function' ? secret(clientId) : secret;\n }\n\n async createAuthorizationUri({\n provider,\n state,\n pkce,\n }: {\n provider: string;\n state: string;\n pkce?: PkceParameters;\n }): Promise<URL> {\n const { implementation, client } = await this.getWebClient(provider);\n return implementation.createAuthorizationUri({\n pkce,\n state,\n scope: client.scope,\n clientId: client.clientId,\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n });\n }\n\n /** Exchanges a code issued to the provider's web client: the redirect flow and One Tap. */\n async exchangeAuthorizationCode({\n provider,\n code,\n pkce,\n }: {\n provider: string;\n code: string;\n pkce?: PkceParameters;\n }): Promise<{ clientId: string; token: OAuth2Token }> {\n const { implementation, client } = await this.getWebClient(provider);\n const { clientId } = client;\n const token = await implementation.exchangeAuthorizationCode({\n code,\n pkce,\n clientId,\n clientSecret: await this.resolveSecret(client.clientSecret, clientId),\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n });\n return { clientId, token };\n }\n\n /** `clientId` is the client the token was issued to, as `exchangeAuthorizationCode` returns it. */\n async getUserInfo({\n provider,\n clientId,\n token,\n response,\n }: {\n provider: string;\n clientId: string;\n token: OAuth2Token | OidcToken;\n response?: AuthorizationResponse;\n }) {\n const { implementation } = await this.getClient(provider, clientId);\n return implementation.getUserInfo(token, { clientId, response });\n }\n\n /** Verifies a Google One Tap / FedCM credential, which is issued to the web client. */\n async getTokenInfo({ provider, credential }: { provider: string; credential: string }) {\n const { implementation, client } = await this.getWebClient(provider);\n if (!isOneTapProvider(implementation)) {\n throw new OAuth2Error(400, 'invalid_request', `Provider ${provider} has no One Tap`);\n }\n const { clientId } = client;\n return { clientId, ...(await implementation.getTokenInfo(credential, clientId)) };\n }\n\n async refreshAccessToken({\n provider,\n clientId,\n refreshToken,\n }: {\n provider: string;\n clientId: string;\n refreshToken: string;\n }): Promise<OAuth2Token> {\n const { implementation, client } = await this.getClient(provider, clientId);\n invariant(implementation.refreshAccessToken, 'Provider does not support refreshAccessToken');\n const clientSecret = await this.resolveSecret(client.clientSecret, clientId);\n return implementation.refreshAccessToken({ refreshToken, clientId, clientSecret });\n }\n\n async revokeToken({\n provider,\n clientId,\n token,\n }: {\n provider: string;\n clientId: string;\n token: string;\n }) {\n const { implementation, client } = await this.getClient(provider, clientId);\n invariant(implementation.revokeToken, 'Provider does not support revokeToken');\n const clientSecret = await this.resolveSecret(client.clientSecret, clientId);\n await implementation.revokeToken({ token, clientId, clientSecret });\n }\n\n async loginOAuth2Native({\n provider,\n credentials,\n pkce,\n }: {\n provider: string;\n credentials: NativeCredential;\n pkce?: PkceParameters;\n }) {\n const { implementation, client } = await this.getNativeClient(provider, credentials.client_id);\n invariant(implementation.loginOAuth2Native, 'Provider does not support loginOAuth2Native');\n const { clientId } = client;\n\n const result = await implementation.loginOAuth2Native({\n clientId,\n clientSecret: await this.resolveSecret(client.clientSecret, clientId),\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n credentials,\n pkce,\n });\n return { clientId, ...result };\n }\n}\n\nfunction isOneTapProvider(provider: Provider): provider is OneTapProvider {\n return typeof (provider as Partial<OneTapProvider>).getTokenInfo === 'function';\n}\n"],"mappings":"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"}
|
package/dist/oauth2/client.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeCredential, OAuth2ClientConfig, OAuth2Token, OidcToken, PkceParameters, UserInfo } from "./types.cjs";
|
|
1
|
+
import { AuthorizationResponse, NativeCredential, OAuth2ClientConfig, OAuth2Token, OidcToken, PkceParameters, UserInfo } from "./types.cjs";
|
|
2
2
|
//#region src/oauth2/client.d.ts
|
|
3
3
|
export declare const oauth2RedirectQuerySchema: import("zod/mini").ZodMiniObject<{
|
|
4
4
|
code: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
@@ -6,6 +6,7 @@ export declare const oauth2RedirectQuerySchema: import("zod/mini").ZodMiniObject
|
|
|
6
6
|
error: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
7
7
|
error_description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
8
8
|
error_uri: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
9
|
+
user: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
9
10
|
}, import("zod/v4/core").$strip>;
|
|
10
11
|
export declare const googleOneTapSchema: import("zod/mini").ZodMiniObject<{
|
|
11
12
|
code: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
@@ -24,33 +25,72 @@ export declare class OAuth2Client {
|
|
|
24
25
|
get baseUri(): string;
|
|
25
26
|
get successUri(): string;
|
|
26
27
|
get errorUri(): string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/** The provider key comes from the request path, so an unknown one is the caller's error. */
|
|
29
|
+
private getProviderConfig;
|
|
30
|
+
private defaultRedirectUri;
|
|
31
|
+
/** The redirect flow and One Tap sign in as the provider's web client. */
|
|
32
|
+
private getWebClient;
|
|
33
|
+
/**
|
|
34
|
+
* Any configured client of the provider, looked up by id: refresh and revoke use the client a
|
|
35
|
+
* token was issued to, and the native login the one the app names. The web client counts too:
|
|
36
|
+
* an Android app's server auth code and a Facebook app's tokens are issued to it.
|
|
37
|
+
*/
|
|
38
|
+
private getClient;
|
|
39
|
+
/**
|
|
40
|
+
* The client a native login signs in as: the one the app names with `client_id`, which must be
|
|
41
|
+
* configured; without one, the only native client, or the web client when there is no native
|
|
42
|
+
* one. Several native clients and no `client_id` is ambiguous and refused rather than guessed.
|
|
43
|
+
*/
|
|
44
|
+
private getNativeClient;
|
|
45
|
+
private resolveSecret;
|
|
46
|
+
createAuthorizationUri({ provider, state, pkce }: {
|
|
47
|
+
provider: string;
|
|
30
48
|
state: string;
|
|
31
49
|
pkce?: PkceParameters;
|
|
32
50
|
}): Promise<URL>;
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
/** Exchanges a code issued to the provider's web client: the redirect flow and One Tap. */
|
|
52
|
+
exchangeAuthorizationCode({ provider, code, pkce }: {
|
|
53
|
+
provider: string;
|
|
35
54
|
code: string;
|
|
36
55
|
pkce?: PkceParameters;
|
|
37
|
-
}): Promise<
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
}): Promise<{
|
|
57
|
+
clientId: string;
|
|
58
|
+
token: OAuth2Token;
|
|
59
|
+
}>;
|
|
60
|
+
/** `clientId` is the client the token was issued to, as `exchangeAuthorizationCode` returns it. */
|
|
61
|
+
getUserInfo({ provider, clientId, token, response }: {
|
|
62
|
+
provider: string;
|
|
63
|
+
clientId: string;
|
|
40
64
|
token: OAuth2Token | OidcToken;
|
|
65
|
+
response?: AuthorizationResponse;
|
|
41
66
|
}): Promise<UserInfo<unknown>>;
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
/** Verifies a Google One Tap / FedCM credential, which is issued to the web client. */
|
|
68
|
+
getTokenInfo({ provider, credential }: {
|
|
69
|
+
provider: string;
|
|
70
|
+
credential: string;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
token: OAuth2Token;
|
|
73
|
+
userInfo: UserInfo;
|
|
74
|
+
clientId: string;
|
|
75
|
+
}>;
|
|
76
|
+
refreshAccessToken({ provider, clientId, refreshToken }: {
|
|
77
|
+
provider: string;
|
|
78
|
+
clientId: string;
|
|
44
79
|
refreshToken: string;
|
|
45
80
|
}): Promise<OAuth2Token>;
|
|
46
|
-
revokeToken(
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
revokeToken({ provider, clientId, token }: {
|
|
82
|
+
provider: string;
|
|
83
|
+
clientId: string;
|
|
84
|
+
token: string;
|
|
85
|
+
}): Promise<void>;
|
|
86
|
+
loginOAuth2Native({ provider, credentials, pkce }: {
|
|
87
|
+
provider: string;
|
|
49
88
|
credentials: NativeCredential;
|
|
50
89
|
pkce?: PkceParameters;
|
|
51
90
|
}): Promise<{
|
|
52
91
|
token: OAuth2Token;
|
|
53
92
|
userInfo: UserInfo;
|
|
93
|
+
clientId: string;
|
|
54
94
|
}>;
|
|
55
95
|
}
|
|
56
96
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.cts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"client.d.cts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAkBa,8CAAyB;;;;;;;yBAQpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;;UAKU;UAUN;;UAKM;;;;;;UAaA;;;;;;UAcA;UAYA;EAIR,yBACJ,UACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;;EAYN,4BACJ,UACA,MACA;IAEA;IACA;IACA,OAAO;MACL;IAAU;IAAkB,OAAO;;;EAcjC,cACJ,UACA,UACA,OACA;IAEA;IACA;IACA,OAAO,cAAc;IACrB,WAAW;MACZ,QADYA;;EAOP,eAAe,UAAU;IAAgB;IAAkB;MAAoB;;;;;EAS/E,qBACJ,UACA,UACA;IAEA;IACA;IACA;MACE,QAAQ;EAON,cACJ,UACA,UACA;IAEA;IACA;IACA;MACD;EAOK,oBACJ,UACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
|
package/dist/oauth2/client.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeCredential, OAuth2ClientConfig, OAuth2Token, OidcToken, PkceParameters, UserInfo } from "./types.mjs";
|
|
1
|
+
import { AuthorizationResponse, NativeCredential, OAuth2ClientConfig, OAuth2Token, OidcToken, PkceParameters, UserInfo } from "./types.mjs";
|
|
2
2
|
//#region src/oauth2/client.d.ts
|
|
3
3
|
export declare const oauth2RedirectQuerySchema: import("zod/mini").ZodMiniObject<{
|
|
4
4
|
code: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
@@ -6,6 +6,7 @@ export declare const oauth2RedirectQuerySchema: import("zod/mini").ZodMiniObject
|
|
|
6
6
|
error: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
7
7
|
error_description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
8
8
|
error_uri: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
9
|
+
user: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
9
10
|
}, import("zod/v4/core").$strip>;
|
|
10
11
|
export declare const googleOneTapSchema: import("zod/mini").ZodMiniObject<{
|
|
11
12
|
code: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
@@ -24,33 +25,72 @@ export declare class OAuth2Client {
|
|
|
24
25
|
get baseUri(): string;
|
|
25
26
|
get successUri(): string;
|
|
26
27
|
get errorUri(): string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/** The provider key comes from the request path, so an unknown one is the caller's error. */
|
|
29
|
+
private getProviderConfig;
|
|
30
|
+
private defaultRedirectUri;
|
|
31
|
+
/** The redirect flow and One Tap sign in as the provider's web client. */
|
|
32
|
+
private getWebClient;
|
|
33
|
+
/**
|
|
34
|
+
* Any configured client of the provider, looked up by id: refresh and revoke use the client a
|
|
35
|
+
* token was issued to, and the native login the one the app names. The web client counts too:
|
|
36
|
+
* an Android app's server auth code and a Facebook app's tokens are issued to it.
|
|
37
|
+
*/
|
|
38
|
+
private getClient;
|
|
39
|
+
/**
|
|
40
|
+
* The client a native login signs in as: the one the app names with `client_id`, which must be
|
|
41
|
+
* configured; without one, the only native client, or the web client when there is no native
|
|
42
|
+
* one. Several native clients and no `client_id` is ambiguous and refused rather than guessed.
|
|
43
|
+
*/
|
|
44
|
+
private getNativeClient;
|
|
45
|
+
private resolveSecret;
|
|
46
|
+
createAuthorizationUri({ provider, state, pkce }: {
|
|
47
|
+
provider: string;
|
|
30
48
|
state: string;
|
|
31
49
|
pkce?: PkceParameters;
|
|
32
50
|
}): Promise<URL>;
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
/** Exchanges a code issued to the provider's web client: the redirect flow and One Tap. */
|
|
52
|
+
exchangeAuthorizationCode({ provider, code, pkce }: {
|
|
53
|
+
provider: string;
|
|
35
54
|
code: string;
|
|
36
55
|
pkce?: PkceParameters;
|
|
37
|
-
}): Promise<
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
}): Promise<{
|
|
57
|
+
clientId: string;
|
|
58
|
+
token: OAuth2Token;
|
|
59
|
+
}>;
|
|
60
|
+
/** `clientId` is the client the token was issued to, as `exchangeAuthorizationCode` returns it. */
|
|
61
|
+
getUserInfo({ provider, clientId, token, response }: {
|
|
62
|
+
provider: string;
|
|
63
|
+
clientId: string;
|
|
40
64
|
token: OAuth2Token | OidcToken;
|
|
65
|
+
response?: AuthorizationResponse;
|
|
41
66
|
}): Promise<UserInfo<unknown>>;
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
/** Verifies a Google One Tap / FedCM credential, which is issued to the web client. */
|
|
68
|
+
getTokenInfo({ provider, credential }: {
|
|
69
|
+
provider: string;
|
|
70
|
+
credential: string;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
token: OAuth2Token;
|
|
73
|
+
userInfo: UserInfo;
|
|
74
|
+
clientId: string;
|
|
75
|
+
}>;
|
|
76
|
+
refreshAccessToken({ provider, clientId, refreshToken }: {
|
|
77
|
+
provider: string;
|
|
78
|
+
clientId: string;
|
|
44
79
|
refreshToken: string;
|
|
45
80
|
}): Promise<OAuth2Token>;
|
|
46
|
-
revokeToken(
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
revokeToken({ provider, clientId, token }: {
|
|
82
|
+
provider: string;
|
|
83
|
+
clientId: string;
|
|
84
|
+
token: string;
|
|
85
|
+
}): Promise<void>;
|
|
86
|
+
loginOAuth2Native({ provider, credentials, pkce }: {
|
|
87
|
+
provider: string;
|
|
49
88
|
credentials: NativeCredential;
|
|
50
89
|
pkce?: PkceParameters;
|
|
51
90
|
}): Promise<{
|
|
52
91
|
token: OAuth2Token;
|
|
53
92
|
userInfo: UserInfo;
|
|
93
|
+
clientId: string;
|
|
54
94
|
}>;
|
|
55
95
|
}
|
|
56
96
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAkBa,8CAAyB;;;;;;;yBAQpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;;UAKU;UAUN;;UAKM;;;;;;UAaA;;;;;;UAcA;UAYA;EAIR,yBACJ,UACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;;EAYN,4BACJ,UACA,MACA;IAEA;IACA;IACA,OAAO;MACL;IAAU;IAAkB,OAAO;;;EAcjC,cACJ,UACA,UACA,OACA;IAEA;IACA;IACA,OAAO,cAAc;IACrB,WAAW;MACZ,QADYA;;EAOP,eAAe,UAAU;IAAgB;IAAkB;MAAoB;;;;;EAS/E,qBACJ,UACA,UACA;IAEA;IACA;IACA;MACE,QAAQ;EAON,cACJ,UACA,UACA;IAEA;IACA;IACA;MACD;EAOK,oBACJ,UACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
|
package/dist/oauth2/client.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OAuth2Error } from "./error.mjs";
|
|
1
2
|
import { invariant } from "@shware/utils";
|
|
2
3
|
import { object, optional, string } from "zod/v4-mini";
|
|
3
4
|
//#region src/oauth2/client.ts
|
|
@@ -6,7 +7,8 @@ const oauth2RedirectQuerySchema = object({
|
|
|
6
7
|
state: optional(string()),
|
|
7
8
|
error: optional(string()),
|
|
8
9
|
error_description: optional(string()),
|
|
9
|
-
error_uri: optional(string())
|
|
10
|
+
error_uri: optional(string()),
|
|
11
|
+
user: optional(string())
|
|
10
12
|
});
|
|
11
13
|
const googleOneTapSchema = object({
|
|
12
14
|
code: optional(string()),
|
|
@@ -33,78 +35,140 @@ var OAuth2Client = class {
|
|
|
33
35
|
get errorUri() {
|
|
34
36
|
return this.config.errorUri;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
38
|
+
/** The provider key comes from the request path, so an unknown one is the caller's error. */
|
|
39
|
+
async getProviderConfig(provider) {
|
|
40
|
+
const providers = typeof this.config.providers === "function" ? await this.config.providers() : this.config.providers;
|
|
41
|
+
const config = Object.hasOwn(providers, provider) ? providers[provider] : void 0;
|
|
42
|
+
if (!config) throw new OAuth2Error(400, "invalid_request", `Unknown provider: ${provider}`);
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
defaultRedirectUri(provider) {
|
|
46
|
+
return `${this.config.baseUri}/login/oauth2/code/${provider}`;
|
|
47
|
+
}
|
|
48
|
+
/** The redirect flow and One Tap sign in as the provider's web client. */
|
|
49
|
+
async getWebClient(provider) {
|
|
50
|
+
const config = await this.getProviderConfig(provider);
|
|
51
|
+
if (!config.web) throw new OAuth2Error(400, "invalid_request", `Provider ${provider} has no web client`);
|
|
52
|
+
return {
|
|
53
|
+
implementation: config.provider,
|
|
54
|
+
client: config.web
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Any configured client of the provider, looked up by id: refresh and revoke use the client a
|
|
59
|
+
* token was issued to, and the native login the one the app names. The web client counts too:
|
|
60
|
+
* an Android app's server auth code and a Facebook app's tokens are issued to it.
|
|
61
|
+
*/
|
|
62
|
+
async getClient(provider, clientId) {
|
|
63
|
+
const config = await this.getProviderConfig(provider);
|
|
64
|
+
const client = [config.web, ...config.native ?? []].find((c) => c?.clientId === clientId);
|
|
65
|
+
if (!client) throw new OAuth2Error(400, "invalid_client", `${clientId} is not a client of ${provider}`);
|
|
41
66
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
implementation: config.provider,
|
|
68
|
+
client
|
|
44
69
|
};
|
|
45
70
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The client a native login signs in as: the one the app names with `client_id`, which must be
|
|
73
|
+
* configured; without one, the only native client, or the web client when there is no native
|
|
74
|
+
* one. Several native clients and no `client_id` is ambiguous and refused rather than guessed.
|
|
75
|
+
*/
|
|
76
|
+
async getNativeClient(provider, clientId) {
|
|
77
|
+
if (clientId !== void 0) return this.getClient(provider, clientId);
|
|
78
|
+
const config = await this.getProviderConfig(provider);
|
|
79
|
+
const native = config.native ?? [];
|
|
80
|
+
const client = native.length === 0 ? config.web : native.length === 1 ? native[0] : void 0;
|
|
81
|
+
if (!client) throw new OAuth2Error(400, "invalid_request", `client_id is required for ${provider}`);
|
|
82
|
+
return {
|
|
83
|
+
implementation: config.provider,
|
|
84
|
+
client
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async resolveSecret(secret, clientId) {
|
|
88
|
+
return typeof secret === "function" ? secret(clientId) : secret;
|
|
89
|
+
}
|
|
90
|
+
async createAuthorizationUri({ provider, state, pkce }) {
|
|
91
|
+
const { implementation, client } = await this.getWebClient(provider);
|
|
92
|
+
return implementation.createAuthorizationUri({
|
|
51
93
|
pkce,
|
|
52
94
|
state,
|
|
53
|
-
scope,
|
|
54
|
-
clientId,
|
|
55
|
-
redirectUri: redirectUri ??
|
|
95
|
+
scope: client.scope,
|
|
96
|
+
clientId: client.clientId,
|
|
97
|
+
redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider)
|
|
56
98
|
});
|
|
57
99
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const {
|
|
61
|
-
const { clientId
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
/** Exchanges a code issued to the provider's web client: the redirect flow and One Tap. */
|
|
101
|
+
async exchangeAuthorizationCode({ provider, code, pkce }) {
|
|
102
|
+
const { implementation, client } = await this.getWebClient(provider);
|
|
103
|
+
const { clientId } = client;
|
|
104
|
+
return {
|
|
105
|
+
clientId,
|
|
106
|
+
token: await implementation.exchangeAuthorizationCode({
|
|
107
|
+
code,
|
|
108
|
+
pkce,
|
|
109
|
+
clientId,
|
|
110
|
+
clientSecret: await this.resolveSecret(client.clientSecret, clientId),
|
|
111
|
+
redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider)
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** `clientId` is the client the token was issued to, as `exchangeAuthorizationCode` returns it. */
|
|
116
|
+
async getUserInfo({ provider, clientId, token, response }) {
|
|
117
|
+
const { implementation } = await this.getClient(provider, clientId);
|
|
118
|
+
return implementation.getUserInfo(token, {
|
|
65
119
|
clientId,
|
|
66
|
-
|
|
67
|
-
redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`
|
|
120
|
+
response
|
|
68
121
|
});
|
|
69
122
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
/** Verifies a Google One Tap / FedCM credential, which is issued to the web client. */
|
|
124
|
+
async getTokenInfo({ provider, credential }) {
|
|
125
|
+
const { implementation, client } = await this.getWebClient(provider);
|
|
126
|
+
if (!isOneTapProvider(implementation)) throw new OAuth2Error(400, "invalid_request", `Provider ${provider} has no One Tap`);
|
|
127
|
+
const { clientId } = client;
|
|
128
|
+
return {
|
|
129
|
+
clientId,
|
|
130
|
+
...await implementation.getTokenInfo(credential, clientId)
|
|
131
|
+
};
|
|
73
132
|
}
|
|
74
|
-
async refreshAccessToken({
|
|
75
|
-
const {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return
|
|
133
|
+
async refreshAccessToken({ provider, clientId, refreshToken }) {
|
|
134
|
+
const { implementation, client } = await this.getClient(provider, clientId);
|
|
135
|
+
invariant(implementation.refreshAccessToken, "Provider does not support refreshAccessToken");
|
|
136
|
+
const clientSecret = await this.resolveSecret(client.clientSecret, clientId);
|
|
137
|
+
return implementation.refreshAccessToken({
|
|
79
138
|
refreshToken,
|
|
80
139
|
clientId,
|
|
81
140
|
clientSecret
|
|
82
141
|
});
|
|
83
142
|
}
|
|
84
|
-
async revokeToken(
|
|
85
|
-
const {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await
|
|
143
|
+
async revokeToken({ provider, clientId, token }) {
|
|
144
|
+
const { implementation, client } = await this.getClient(provider, clientId);
|
|
145
|
+
invariant(implementation.revokeToken, "Provider does not support revokeToken");
|
|
146
|
+
const clientSecret = await this.resolveSecret(client.clientSecret, clientId);
|
|
147
|
+
await implementation.revokeToken({
|
|
89
148
|
token,
|
|
90
149
|
clientId,
|
|
91
150
|
clientSecret
|
|
92
151
|
});
|
|
93
152
|
}
|
|
94
|
-
async loginOAuth2Native({
|
|
95
|
-
const {
|
|
96
|
-
invariant(
|
|
97
|
-
const {
|
|
98
|
-
|
|
99
|
-
return provider.loginOAuth2Native({
|
|
153
|
+
async loginOAuth2Native({ provider, credentials, pkce }) {
|
|
154
|
+
const { implementation, client } = await this.getNativeClient(provider, credentials.client_id);
|
|
155
|
+
invariant(implementation.loginOAuth2Native, "Provider does not support loginOAuth2Native");
|
|
156
|
+
const { clientId } = client;
|
|
157
|
+
return {
|
|
100
158
|
clientId,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
159
|
+
...await implementation.loginOAuth2Native({
|
|
160
|
+
clientId,
|
|
161
|
+
clientSecret: await this.resolveSecret(client.clientSecret, clientId),
|
|
162
|
+
redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),
|
|
163
|
+
credentials,
|
|
164
|
+
pkce
|
|
165
|
+
})
|
|
166
|
+
};
|
|
106
167
|
}
|
|
107
168
|
};
|
|
169
|
+
function isOneTapProvider(provider) {
|
|
170
|
+
return typeof provider.getTokenInfo === "function";
|
|
171
|
+
}
|
|
108
172
|
//#endregion
|
|
109
173
|
export { OAuth2Client, googleOneTapSchema, oauth2RedirectQuerySchema };
|
|
110
174
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/oauth2/client.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { object, optional, string } from 'zod/v4-mini';\nimport type {\n NativeCredential,\n OAuth2ClientConfig,\n OAuth2Token,\n OidcToken,\n PkceParameters,\n} from './types';\n\nexport const oauth2RedirectQuerySchema = object({\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n});\n\nexport const googleOneTapSchema = object({\n // login_uri\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n nonce: optional(string()),\n g_csrf_token: optional(string()),\n hd: optional(string()),\n // callback\n credential: optional(string()),\n});\n\nexport class OAuth2Client {\n private readonly config: OAuth2ClientConfig;\n constructor(config: OAuth2ClientConfig) {\n this.config = config;\n }\n\n get baseUri() {\n return this.config.baseUri;\n }\n\n get successUri() {\n return this.config.successUri;\n }\n\n get errorUri() {\n return this.config.errorUri;\n }\n\n private async getClientConfig(registrationId: string) {\n const registrations =\n typeof this.config.registration === 'function'\n ? await this.config.registration()\n : this.config.registration;\n\n const registration = registrations[registrationId];\n invariant(registration, `Registration ${registrationId} not found`);\n\n const provider = this.config.provider[registration.provider ?? registrationId];\n invariant(provider, `Provider ${registration.provider ?? registrationId} not found`);\n return { registration, provider };\n }\n\n async createAuthorizationUri({\n registrationId,\n state,\n pkce,\n }: {\n registrationId: string;\n state: string;\n pkce?: PkceParameters;\n }): Promise<URL> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n\n const { baseUri } = this.config;\n const { scope, clientId, redirectUri } = registration;\n return provider.createAuthorizationUri({\n pkce,\n state,\n scope,\n clientId,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n });\n }\n\n async exchangeAuthorizationCode({\n registrationId,\n code,\n pkce,\n }: {\n registrationId: string;\n code: string;\n pkce?: PkceParameters;\n }): Promise<OAuth2Token> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n\n const { baseUri } = this.config;\n const { clientId, clientSecret, redirectUri } = registration;\n return provider.exchangeAuthorizationCode({\n code,\n pkce,\n clientId,\n clientSecret,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n });\n }\n\n async getUserInfo({\n registrationId,\n token,\n }: {\n registrationId: string;\n token: OAuth2Token | OidcToken;\n }) {\n const { provider } = await this.getClientConfig(registrationId);\n return provider.getUserInfo(token);\n }\n\n async refreshAccessToken({\n registrationId,\n refreshToken,\n }: {\n registrationId: string;\n refreshToken: string;\n }): Promise<OAuth2Token> {\n const { provider, registration } = await this.getClientConfig(registrationId);\n const { clientId, clientSecret } = registration;\n\n invariant(provider.refreshAccessToken, 'Provider does not support refreshAccessToken');\n return provider.refreshAccessToken({ refreshToken, clientId, clientSecret });\n }\n\n async revokeToken(registrationId: string, token: string) {\n const { provider, registration } = await this.getClientConfig(registrationId);\n const { clientId, clientSecret } = registration;\n\n invariant(provider.revokeToken, 'Provider does not support revokeToken');\n await provider.revokeToken({ token, clientId, clientSecret });\n }\n\n async loginOAuth2Native({\n registrationId,\n credentials,\n pkce,\n }: {\n registrationId: string;\n credentials: NativeCredential;\n pkce?: PkceParameters;\n }) {\n const { provider, registration } = await this.getClientConfig(registrationId);\n invariant(provider.loginOAuth2Native, 'Provider does not support loginOAuth2Native');\n const { baseUri } = this.config;\n const { clientId, clientSecret, redirectUri } = registration;\n\n return provider.loginOAuth2Native({\n clientId,\n clientSecret,\n redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`,\n credentials,\n pkce,\n });\n }\n}\n"],"mappings":";;;AAUA,MAAa,4BAA4B,OAAO;CAC9C,MAAM,SAAS,OAAO,CAAC;CACvB,OAAO,SAAS,OAAO,CAAC;CACxB,OAAO,SAAS,OAAO,CAAC;CACxB,mBAAmB,SAAS,OAAO,CAAC;CACpC,WAAW,SAAS,OAAO,CAAC;AAC9B,CAAC;AAED,MAAa,qBAAqB,OAAO;CAEvC,MAAM,SAAS,OAAO,CAAC;CACvB,OAAO,SAAS,OAAO,CAAC;CACxB,OAAO,SAAS,OAAO,CAAC;CACxB,mBAAmB,SAAS,OAAO,CAAC;CACpC,WAAW,SAAS,OAAO,CAAC;CAC5B,OAAO,SAAS,OAAO,CAAC;CACxB,cAAc,SAAS,OAAO,CAAC;CAC/B,IAAI,SAAS,OAAO,CAAC;CAErB,YAAY,SAAS,OAAO,CAAC;AAC/B,CAAC;AAED,IAAa,eAAb,MAA0B;CACxB;CACA,YAAY,QAA4B;EACtC,KAAK,SAAS;CAChB;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,aAAa;EACf,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,WAAW;EACb,OAAO,KAAK,OAAO;CACrB;CAEA,MAAc,gBAAgB,gBAAwB;EAMpD,MAAM,gBAJJ,OAAO,KAAK,OAAO,iBAAiB,aAChC,MAAM,KAAK,OAAO,aAAa,IAC/B,KAAK,OAAO,aAAA,CAEiB;EACnC,UAAU,cAAc,gBAAgB,eAAe,WAAW;EAElE,MAAM,WAAW,KAAK,OAAO,SAAS,aAAa,YAAY;EAC/D,UAAU,UAAU,YAAY,aAAa,YAAY,eAAe,WAAW;EACnF,OAAO;GAAE;GAAc;EAAS;CAClC;CAEA,MAAM,uBAAuB,EAC3B,gBACA,OACA,QAKe;EACf,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAE5E,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,OAAO,UAAU,gBAAgB;EACzC,OAAO,SAAS,uBAAuB;GACrC;GACA;GACA;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;EAC9D,CAAC;CACH;CAEA,MAAM,0BAA0B,EAC9B,gBACA,MACA,QAKuB;EACvB,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAE5E,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,UAAU,cAAc,gBAAgB;EAChD,OAAO,SAAS,0BAA0B;GACxC;GACA;GACA;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;EAC9D,CAAC;CACH;CAEA,MAAM,YAAY,EAChB,gBACA,SAIC;EACD,MAAM,EAAE,aAAa,MAAM,KAAK,gBAAgB,cAAc;EAC9D,OAAO,SAAS,YAAY,KAAK;CACnC;CAEA,MAAM,mBAAmB,EACvB,gBACA,gBAIuB;EACvB,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,MAAM,EAAE,UAAU,iBAAiB;EAEnC,UAAU,SAAS,oBAAoB,8CAA8C;EACrF,OAAO,SAAS,mBAAmB;GAAE;GAAc;GAAU;EAAa,CAAC;CAC7E;CAEA,MAAM,YAAY,gBAAwB,OAAe;EACvD,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,MAAM,EAAE,UAAU,iBAAiB;EAEnC,UAAU,SAAS,aAAa,uCAAuC;EACvE,MAAM,SAAS,YAAY;GAAE;GAAO;GAAU;EAAa,CAAC;CAC9D;CAEA,MAAM,kBAAkB,EACtB,gBACA,aACA,QAKC;EACD,MAAM,EAAE,UAAU,iBAAiB,MAAM,KAAK,gBAAgB,cAAc;EAC5E,UAAU,SAAS,mBAAmB,6CAA6C;EACnF,MAAM,EAAE,YAAY,KAAK;EACzB,MAAM,EAAE,UAAU,cAAc,gBAAgB;EAEhD,OAAO,SAAS,kBAAkB;GAChC;GACA;GACA,aAAa,eAAe,GAAG,QAAQ,qBAAqB;GAC5D;GACA;EACF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/oauth2/client.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { object, optional, string } from 'zod/v4-mini';\nimport { OAuth2Error } from './error';\nimport type {\n AuthorizationResponse,\n ClientSecret,\n NativeClient,\n NativeCredential,\n OAuth2ClientConfig,\n OAuth2Token,\n OidcToken,\n OneTapProvider,\n PkceParameters,\n Provider,\n ProviderConfig,\n WebClient,\n} from './types';\n\nexport const oauth2RedirectQuerySchema = object({\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n // Apple's form_post: the user's name as JSON, first authorization only.\n user: optional(string()),\n});\n\nexport const googleOneTapSchema = object({\n // login_uri\n code: optional(string()),\n state: optional(string()),\n error: optional(string()),\n error_description: optional(string()),\n error_uri: optional(string()),\n nonce: optional(string()),\n g_csrf_token: optional(string()),\n hd: optional(string()),\n // callback\n credential: optional(string()),\n});\n\nexport class OAuth2Client {\n private readonly config: OAuth2ClientConfig;\n constructor(config: OAuth2ClientConfig) {\n this.config = config;\n }\n\n get baseUri() {\n return this.config.baseUri;\n }\n\n get successUri() {\n return this.config.successUri;\n }\n\n get errorUri() {\n return this.config.errorUri;\n }\n\n /** The provider key comes from the request path, so an unknown one is the caller's error. */\n private async getProviderConfig(provider: string): Promise<ProviderConfig> {\n const providers =\n typeof this.config.providers === 'function'\n ? await this.config.providers()\n : this.config.providers;\n const config = Object.hasOwn(providers, provider) ? providers[provider] : undefined;\n if (!config) throw new OAuth2Error(400, 'invalid_request', `Unknown provider: ${provider}`);\n return config;\n }\n\n private defaultRedirectUri(provider: string) {\n return `${this.config.baseUri}/login/oauth2/code/${provider}`;\n }\n\n /** The redirect flow and One Tap sign in as the provider's web client. */\n private async getWebClient(provider: string) {\n const config = await this.getProviderConfig(provider);\n if (!config.web) {\n throw new OAuth2Error(400, 'invalid_request', `Provider ${provider} has no web client`);\n }\n return { implementation: config.provider, client: config.web };\n }\n\n /**\n * Any configured client of the provider, looked up by id: refresh and revoke use the client a\n * token was issued to, and the native login the one the app names. The web client counts too:\n * an Android app's server auth code and a Facebook app's tokens are issued to it.\n */\n private async getClient(provider: string, clientId: string) {\n const config = await this.getProviderConfig(provider);\n const client = [config.web, ...(config.native ?? [])].find((c) => c?.clientId === clientId);\n if (!client) {\n throw new OAuth2Error(400, 'invalid_client', `${clientId} is not a client of ${provider}`);\n }\n return { implementation: config.provider, client };\n }\n\n /**\n * The client a native login signs in as: the one the app names with `client_id`, which must be\n * configured; without one, the only native client, or the web client when there is no native\n * one. Several native clients and no `client_id` is ambiguous and refused rather than guessed.\n */\n private async getNativeClient(provider: string, clientId: string | undefined) {\n if (clientId !== undefined) return this.getClient(provider, clientId);\n const config = await this.getProviderConfig(provider);\n const native = config.native ?? [];\n const client: WebClient | NativeClient | undefined =\n native.length === 0 ? config.web : native.length === 1 ? native[0] : undefined;\n if (!client) {\n throw new OAuth2Error(400, 'invalid_request', `client_id is required for ${provider}`);\n }\n return { implementation: config.provider, client };\n }\n\n private async resolveSecret(secret: ClientSecret | undefined, clientId: string) {\n return typeof secret === 'function' ? secret(clientId) : secret;\n }\n\n async createAuthorizationUri({\n provider,\n state,\n pkce,\n }: {\n provider: string;\n state: string;\n pkce?: PkceParameters;\n }): Promise<URL> {\n const { implementation, client } = await this.getWebClient(provider);\n return implementation.createAuthorizationUri({\n pkce,\n state,\n scope: client.scope,\n clientId: client.clientId,\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n });\n }\n\n /** Exchanges a code issued to the provider's web client: the redirect flow and One Tap. */\n async exchangeAuthorizationCode({\n provider,\n code,\n pkce,\n }: {\n provider: string;\n code: string;\n pkce?: PkceParameters;\n }): Promise<{ clientId: string; token: OAuth2Token }> {\n const { implementation, client } = await this.getWebClient(provider);\n const { clientId } = client;\n const token = await implementation.exchangeAuthorizationCode({\n code,\n pkce,\n clientId,\n clientSecret: await this.resolveSecret(client.clientSecret, clientId),\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n });\n return { clientId, token };\n }\n\n /** `clientId` is the client the token was issued to, as `exchangeAuthorizationCode` returns it. */\n async getUserInfo({\n provider,\n clientId,\n token,\n response,\n }: {\n provider: string;\n clientId: string;\n token: OAuth2Token | OidcToken;\n response?: AuthorizationResponse;\n }) {\n const { implementation } = await this.getClient(provider, clientId);\n return implementation.getUserInfo(token, { clientId, response });\n }\n\n /** Verifies a Google One Tap / FedCM credential, which is issued to the web client. */\n async getTokenInfo({ provider, credential }: { provider: string; credential: string }) {\n const { implementation, client } = await this.getWebClient(provider);\n if (!isOneTapProvider(implementation)) {\n throw new OAuth2Error(400, 'invalid_request', `Provider ${provider} has no One Tap`);\n }\n const { clientId } = client;\n return { clientId, ...(await implementation.getTokenInfo(credential, clientId)) };\n }\n\n async refreshAccessToken({\n provider,\n clientId,\n refreshToken,\n }: {\n provider: string;\n clientId: string;\n refreshToken: string;\n }): Promise<OAuth2Token> {\n const { implementation, client } = await this.getClient(provider, clientId);\n invariant(implementation.refreshAccessToken, 'Provider does not support refreshAccessToken');\n const clientSecret = await this.resolveSecret(client.clientSecret, clientId);\n return implementation.refreshAccessToken({ refreshToken, clientId, clientSecret });\n }\n\n async revokeToken({\n provider,\n clientId,\n token,\n }: {\n provider: string;\n clientId: string;\n token: string;\n }) {\n const { implementation, client } = await this.getClient(provider, clientId);\n invariant(implementation.revokeToken, 'Provider does not support revokeToken');\n const clientSecret = await this.resolveSecret(client.clientSecret, clientId);\n await implementation.revokeToken({ token, clientId, clientSecret });\n }\n\n async loginOAuth2Native({\n provider,\n credentials,\n pkce,\n }: {\n provider: string;\n credentials: NativeCredential;\n pkce?: PkceParameters;\n }) {\n const { implementation, client } = await this.getNativeClient(provider, credentials.client_id);\n invariant(implementation.loginOAuth2Native, 'Provider does not support loginOAuth2Native');\n const { clientId } = client;\n\n const result = await implementation.loginOAuth2Native({\n clientId,\n clientSecret: await this.resolveSecret(client.clientSecret, clientId),\n redirectUri: client.redirectUri ?? this.defaultRedirectUri(provider),\n credentials,\n pkce,\n });\n return { clientId, ...result };\n }\n}\n\nfunction isOneTapProvider(provider: Provider): provider is OneTapProvider {\n return typeof (provider as Partial<OneTapProvider>).getTokenInfo === 'function';\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/oauth2/name.ts
|
|
3
|
+
/**
|
|
4
|
+
* Upper bound, in characters, on each name part taken from the client. Names that reach us outside
|
|
5
|
+
* a signed token (Apple's form_post `user`, a native login body) are plain client input, so they
|
|
6
|
+
* are capped before a host stores them; 64 leaves room for any real given or family name.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_NAME_PART_LENGTH = 64;
|
|
9
|
+
/** Apple's form_post `user` is a few dozen bytes; anything far larger is not Apple's. */
|
|
10
|
+
const MAX_APPLE_USER_LENGTH = 1024;
|
|
11
|
+
const CJK = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+$/u;
|
|
12
|
+
/**
|
|
13
|
+
* Composes a display `name` from its parts, for providers that hand out the parts only. Chinese,
|
|
14
|
+
* Japanese and Korean names are written family name first without a space (`王小明`), every other
|
|
15
|
+
* name given name first (`John Appleseed`), which is also the safe choice for mixed scripts. With
|
|
16
|
+
* one part only, that part is the name.
|
|
17
|
+
*/
|
|
18
|
+
function composeName({ given_name, family_name }) {
|
|
19
|
+
const given = given_name?.trim();
|
|
20
|
+
const family = family_name?.trim();
|
|
21
|
+
if (given && family) return CJK.test(given) && CJK.test(family) ? `${family}${given}` : `${given} ${family}`;
|
|
22
|
+
return given || family || void 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One untrusted name part: a non-blank string, trimmed and capped at `MAX_NAME_PART_LENGTH`
|
|
26
|
+
* characters (code points, so a cut never splits one). Anything else is dropped rather than refused,
|
|
27
|
+
* because the name is a nicety and must never cost the user their login.
|
|
28
|
+
*/
|
|
29
|
+
function cleanNamePart(value) {
|
|
30
|
+
if (typeof value !== "string") return void 0;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
if (!trimmed) return void 0;
|
|
33
|
+
return Array.from(trimmed).slice(0, 64).join("");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parses Apple's form_post `user` (JSON, first authorization only). Malformed JSON or a wrong shape
|
|
37
|
+
* yields `undefined`, never an error. Only known fields are kept, each cleaned, so what a host
|
|
38
|
+
* persists stays small.
|
|
39
|
+
*/
|
|
40
|
+
function parseAppleUser(raw) {
|
|
41
|
+
if (!raw || raw.length > MAX_APPLE_USER_LENGTH) return void 0;
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = JSON.parse(raw);
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!isObject(json)) return void 0;
|
|
49
|
+
const user = {};
|
|
50
|
+
if (isObject(json.name)) {
|
|
51
|
+
const firstName = cleanNamePart(json.name.firstName);
|
|
52
|
+
const lastName = cleanNamePart(json.name.lastName);
|
|
53
|
+
if (firstName || lastName) user.name = withoutUndefined({
|
|
54
|
+
firstName,
|
|
55
|
+
lastName
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const email = typeof json.email === "string" ? json.email.trim().slice(0, 320) : void 0;
|
|
59
|
+
if (email) user.email = email;
|
|
60
|
+
return user.name || user.email ? user : void 0;
|
|
61
|
+
}
|
|
62
|
+
const PERSON_NAME_KEYS = [
|
|
63
|
+
"givenName",
|
|
64
|
+
"familyName",
|
|
65
|
+
"middleName",
|
|
66
|
+
"namePrefix",
|
|
67
|
+
"nameSuffix",
|
|
68
|
+
"nickname"
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* Cleans the `fullName` of a native login body (iOS `PersonNameComponents`): known keys only,
|
|
72
|
+
* each a cleaned string; `undefined` when nothing usable is left.
|
|
73
|
+
*/
|
|
74
|
+
function cleanPersonName(value) {
|
|
75
|
+
if (!isObject(value)) return void 0;
|
|
76
|
+
const name = {};
|
|
77
|
+
for (const key of PERSON_NAME_KEYS) {
|
|
78
|
+
const part = cleanNamePart(value[key]);
|
|
79
|
+
if (part) name[key] = part;
|
|
80
|
+
}
|
|
81
|
+
return Object.keys(name).length > 0 ? name : void 0;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The standard name claims from cleaned parts, with `name` composed from given and family name.
|
|
85
|
+
* Undefined parts are left out, so spreading the result never overwrites a claim with `undefined`.
|
|
86
|
+
*/
|
|
87
|
+
function nameClaims(parts) {
|
|
88
|
+
return withoutUndefined({
|
|
89
|
+
...parts,
|
|
90
|
+
name: composeName(parts)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function isObject(value) {
|
|
94
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
|
+
}
|
|
96
|
+
function withoutUndefined(value) {
|
|
97
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
exports.MAX_NAME_PART_LENGTH = MAX_NAME_PART_LENGTH;
|
|
101
|
+
exports.cleanNamePart = cleanNamePart;
|
|
102
|
+
exports.cleanPersonName = cleanPersonName;
|
|
103
|
+
exports.composeName = composeName;
|
|
104
|
+
exports.nameClaims = nameClaims;
|
|
105
|
+
exports.parseAppleUser = parseAppleUser;
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=name.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name.cjs","names":[],"sources":["../../src/oauth2/name.ts"],"sourcesContent":["import type { PersonNameComponents, StandardClaims } from './types';\n\n/**\n * Upper bound, in characters, on each name part taken from the client. Names that reach us outside\n * a signed token (Apple's form_post `user`, a native login body) are plain client input, so they\n * are capped before a host stores them; 64 leaves room for any real given or family name.\n */\nexport const MAX_NAME_PART_LENGTH = 64;\n\n/** Apple's form_post `user` is a few dozen bytes; anything far larger is not Apple's. */\nconst MAX_APPLE_USER_LENGTH = 1024;\n\nconst CJK = /^[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+$/u;\n\n/**\n * Composes a display `name` from its parts, for providers that hand out the parts only. Chinese,\n * Japanese and Korean names are written family name first without a space (`王小明`), every other\n * name given name first (`John Appleseed`), which is also the safe choice for mixed scripts. With\n * one part only, that part is the name.\n */\nexport function composeName({\n given_name,\n family_name,\n}: Pick<StandardClaims, 'given_name' | 'family_name'>): string | undefined {\n const given = given_name?.trim();\n const family = family_name?.trim();\n if (given && family) {\n return CJK.test(given) && CJK.test(family) ? `${family}${given}` : `${given} ${family}`;\n }\n return given || family || undefined;\n}\n\n/**\n * One untrusted name part: a non-blank string, trimmed and capped at `MAX_NAME_PART_LENGTH`\n * characters (code points, so a cut never splits one). Anything else is dropped rather than refused,\n * because the name is a nicety and must never cost the user their login.\n */\nexport function cleanNamePart(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return Array.from(trimmed).slice(0, MAX_NAME_PART_LENGTH).join('');\n}\n\n/** Name parts as Apple's form_post `user` carries them, after cleaning. */\nexport interface AppleUser {\n name?: { firstName?: string; lastName?: string };\n email?: string;\n}\n\n/**\n * Parses Apple's form_post `user` (JSON, first authorization only). Malformed JSON or a wrong shape\n * yields `undefined`, never an error. Only known fields are kept, each cleaned, so what a host\n * persists stays small.\n */\nexport function parseAppleUser(raw: string | undefined): AppleUser | undefined {\n if (!raw || raw.length > MAX_APPLE_USER_LENGTH) return undefined;\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isObject(json)) return undefined;\n const user: AppleUser = {};\n if (isObject(json.name)) {\n const firstName = cleanNamePart(json.name.firstName);\n const lastName = cleanNamePart(json.name.lastName);\n if (firstName || lastName) user.name = withoutUndefined({ firstName, lastName });\n }\n // Kept as Apple sent it for the record only: the id token's email is signed, this one is not.\n const email = typeof json.email === 'string' ? json.email.trim().slice(0, 320) : undefined;\n if (email) user.email = email;\n return user.name || user.email ? user : undefined;\n}\n\nconst PERSON_NAME_KEYS = [\n 'givenName',\n 'familyName',\n 'middleName',\n 'namePrefix',\n 'nameSuffix',\n 'nickname',\n] as const satisfies (keyof PersonNameComponents)[];\n\n/**\n * Cleans the `fullName` of a native login body (iOS `PersonNameComponents`): known keys only,\n * each a cleaned string; `undefined` when nothing usable is left.\n */\nexport function cleanPersonName(value: unknown): PersonNameComponents | undefined {\n if (!isObject(value)) return undefined;\n const name: PersonNameComponents = {};\n for (const key of PERSON_NAME_KEYS) {\n const part = cleanNamePart(value[key]);\n if (part) name[key] = part;\n }\n return Object.keys(name).length > 0 ? name : undefined;\n}\n\ntype NameClaims = Pick<\n StandardClaims,\n 'name' | 'given_name' | 'family_name' | 'middle_name' | 'nickname'\n>;\n\n/**\n * The standard name claims from cleaned parts, with `name` composed from given and family name.\n * Undefined parts are left out, so spreading the result never overwrites a claim with `undefined`.\n */\nexport function nameClaims(parts: Omit<NameClaims, 'name'>): NameClaims {\n return withoutUndefined({ ...parts, name: composeName(parts) });\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction withoutUndefined<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n"],"mappings":";;;;;;;AAOA,MAAa,uBAAuB;;AAGpC,MAAM,wBAAwB;AAE9B,MAAM,MAAM;;;;;;;AAQZ,SAAgB,YAAY,EAC1B,YACA,eACyE;CACzE,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,SAAS,aAAa,KAAK;CACjC,IAAI,SAAS,QACX,OAAO,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,GAAG,SAAS,UAAU,GAAG,MAAM,GAAG;CAEjF,OAAO,SAAS,UAAU,KAAA;AAC5B;;;;;;AAOA,SAAgB,cAAc,OAAoC;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM,GAAA,EAAuB,CAAC,CAAC,KAAK,EAAE;AACnE;;;;;;AAaA,SAAgB,eAAe,KAAgD;CAC7E,IAAI,CAAC,OAAO,IAAI,SAAS,uBAAuB,OAAO,KAAA;CACvD,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,GAAG;CACvB,QAAQ;EACN;CACF;CACA,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAA;CAC5B,MAAM,OAAkB,CAAC;CACzB,IAAI,SAAS,KAAK,IAAI,GAAG;EACvB,MAAM,YAAY,cAAc,KAAK,KAAK,SAAS;EACnD,MAAM,WAAW,cAAc,KAAK,KAAK,QAAQ;EACjD,IAAI,aAAa,UAAU,KAAK,OAAO,iBAAiB;GAAE;GAAW;EAAS,CAAC;CACjF;CAEA,MAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,IAAI,KAAA;CACjF,IAAI,OAAO,KAAK,QAAQ;CACxB,OAAO,KAAK,QAAQ,KAAK,QAAQ,OAAO,KAAA;AAC1C;AAEA,MAAM,mBAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,SAAgB,gBAAgB,OAAkD;CAChF,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,OAA6B,CAAC;CACpC,KAAK,MAAM,OAAO,kBAAkB;EAClC,MAAM,OAAO,cAAc,MAAM,IAAI;EACrC,IAAI,MAAM,KAAK,OAAO;CACxB;CACA,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,KAAA;AAC/C;;;;;AAWA,SAAgB,WAAW,OAA6C;CACtE,OAAO,iBAAiB;EAAE,GAAG;EAAO,MAAM,YAAY,KAAK;CAAE,CAAC;AAChE;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAmC,OAAa;CACvD,OAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,GAAG,OAAO,MAAM,KAAA,CAAS,CAAC;AACpF"}
|