@shware/security 3.4.1 → 4.1.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.
Files changed (64) hide show
  1. package/README.md +40 -6
  2. package/dist/auth/attributes.cjs +31 -0
  3. package/dist/auth/attributes.cjs.map +1 -0
  4. package/dist/auth/attributes.d.cts +17 -0
  5. package/dist/auth/attributes.d.cts.map +1 -0
  6. package/dist/auth/attributes.d.mts +17 -0
  7. package/dist/auth/attributes.d.mts.map +1 -0
  8. package/dist/auth/attributes.mjs +27 -0
  9. package/dist/auth/attributes.mjs.map +1 -0
  10. package/dist/auth/index.cjs +75 -21
  11. package/dist/auth/index.cjs.map +1 -1
  12. package/dist/auth/index.d.cts +2 -2
  13. package/dist/auth/index.d.cts.map +1 -1
  14. package/dist/auth/index.d.mts +2 -2
  15. package/dist/auth/index.d.mts.map +1 -1
  16. package/dist/auth/index.mjs +75 -21
  17. package/dist/auth/index.mjs.map +1 -1
  18. package/dist/auth/types.d.cts +32 -2
  19. package/dist/auth/types.d.cts.map +1 -1
  20. package/dist/auth/types.d.mts +32 -2
  21. package/dist/auth/types.d.mts.map +1 -1
  22. package/dist/email/schema.cjs +3 -1
  23. package/dist/email/schema.cjs.map +1 -1
  24. package/dist/email/schema.d.cts +1 -0
  25. package/dist/email/schema.d.cts.map +1 -1
  26. package/dist/email/schema.d.mts +1 -0
  27. package/dist/email/schema.d.mts.map +1 -1
  28. package/dist/email/schema.mjs +3 -1
  29. package/dist/email/schema.mjs.map +1 -1
  30. package/dist/index.cjs +3 -0
  31. package/dist/index.d.cts +4 -3
  32. package/dist/index.d.mts +4 -3
  33. package/dist/index.mjs +2 -1
  34. package/dist/oauth2/client.cjs +4 -3
  35. package/dist/oauth2/client.cjs.map +1 -1
  36. package/dist/oauth2/client.d.cts +4 -2
  37. package/dist/oauth2/client.d.cts.map +1 -1
  38. package/dist/oauth2/client.d.mts +4 -2
  39. package/dist/oauth2/client.d.mts.map +1 -1
  40. package/dist/oauth2/client.mjs +4 -3
  41. package/dist/oauth2/client.mjs.map +1 -1
  42. package/dist/oauth2/name.cjs +107 -0
  43. package/dist/oauth2/name.cjs.map +1 -0
  44. package/dist/oauth2/name.d.cts +49 -0
  45. package/dist/oauth2/name.d.cts.map +1 -0
  46. package/dist/oauth2/name.d.mts +49 -0
  47. package/dist/oauth2/name.d.mts.map +1 -0
  48. package/dist/oauth2/name.mjs +101 -0
  49. package/dist/oauth2/name.mjs.map +1 -0
  50. package/dist/oauth2/provider/apple.cjs +32 -10
  51. package/dist/oauth2/provider/apple.cjs.map +1 -1
  52. package/dist/oauth2/provider/apple.d.cts +19 -14
  53. package/dist/oauth2/provider/apple.d.cts.map +1 -1
  54. package/dist/oauth2/provider/apple.d.mts +19 -14
  55. package/dist/oauth2/provider/apple.d.mts.map +1 -1
  56. package/dist/oauth2/provider/apple.mjs +32 -10
  57. package/dist/oauth2/provider/apple.mjs.map +1 -1
  58. package/dist/oauth2/types.cjs.map +1 -1
  59. package/dist/oauth2/types.d.cts +40 -2
  60. package/dist/oauth2/types.d.cts.map +1 -1
  61. package/dist/oauth2/types.d.mts +40 -2
  62. package/dist/oauth2/types.d.mts.map +1 -1
  63. package/dist/oauth2/types.mjs.map +1 -1
  64. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_oauth2_client = require("./oauth2/client.cjs");
3
3
  const require_oauth2_error = require("./oauth2/error.cjs");
4
+ const require_oauth2_name = require("./oauth2/name.cjs");
4
5
  const require_session_common = require("./session/common.cjs");
5
6
  const require_auth_index = require("./auth/index.cjs");
6
7
  const require_session_redis = require("./session/redis.cjs");
@@ -9,6 +10,7 @@ const require_core_index = require("./core/index.cjs");
9
10
  const require_oauth2_types = require("./oauth2/types.cjs");
10
11
  exports.Auth = require_auth_index.Auth;
11
12
  exports.DBIndexedSessionRepository = require_session_db.DBIndexedSessionRepository;
13
+ exports.MAX_NAME_PART_LENGTH = require_oauth2_name.MAX_NAME_PART_LENGTH;
12
14
  exports.OAuth2Client = require_oauth2_client.OAuth2Client;
13
15
  exports.OAuth2Error = require_oauth2_error.OAuth2Error;
14
16
  exports.OIDC_SCOPES = require_oauth2_types.OIDC_SCOPES;
@@ -17,4 +19,5 @@ exports.PRINCIPAL_NAME_INDEX_NAME = require_session_common.PRINCIPAL_NAME_INDEX_
17
19
  exports.PROVIDERS = require_core_index.PROVIDERS;
18
20
  exports.RedisIndexedSessionRepository = require_session_redis.RedisIndexedSessionRepository;
19
21
  exports.RedisKVRepository = require_session_redis.RedisKVRepository;
22
+ exports.composeName = require_oauth2_name.composeName;
20
23
  exports.oauth2RedirectQuerySchema = require_oauth2_client.oauth2RedirectQuerySchema;
package/dist/index.d.cts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { CsrfToken, PROVIDERS, Principal, Provider } from "./core/index.cjs";
2
2
  import { KVRepository, Namespace, Session, SessionRepository } from "./session/types.cjs";
3
- import { NativeCredential, OAuth2Token, OIDC_SCOPES, OidcScope, OidcToken, StandardClaims, UserInfo } from "./oauth2/types.cjs";
4
- import { AuthConfig, AuthService } from "./auth/types.cjs";
3
+ import { AuthorizationResponse, NativeCredential, OAuth2Token, OIDC_SCOPES, OidcScope, OidcToken, PersonNameComponents, StandardClaims, UserInfo } from "./oauth2/types.cjs";
4
+ import { AuthConfig, AuthService, Authorization, AuthorizedHandler, OAuth2AuthorizationOptions } from "./auth/types.cjs";
5
5
  import { Auth, PATH } from "./auth/index.cjs";
6
6
  import { OAuth2Client, oauth2RedirectQuerySchema } from "./oauth2/client.cjs";
7
7
  import { OAuth2Error, OAuth2ErrorType } from "./oauth2/error.cjs";
8
8
  import { RedisIndexedSessionRepository, RedisKVRepository } from "./session/redis.cjs";
9
9
  import { DBAdapter, DBIndexedSessionRepository } from "./session/db.cjs";
10
10
  import { PRINCIPAL_NAME_INDEX_NAME } from "./session/common.cjs";
11
- export { Auth, type AuthConfig, type AuthService, type CsrfToken, type DBAdapter, DBIndexedSessionRepository, type KVRepository, type Namespace, type NativeCredential, OAuth2Client, OAuth2Error, type OAuth2ErrorType, type OAuth2Token, OIDC_SCOPES, type OidcScope, type OidcToken, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, type Principal, type Provider, RedisIndexedSessionRepository, RedisKVRepository, type Session, type SessionRepository, type StandardClaims, type UserInfo, oauth2RedirectQuerySchema };
11
+ import { MAX_NAME_PART_LENGTH, composeName } from "./oauth2/name.cjs";
12
+ export { Auth, type AuthConfig, type AuthService, type Authorization, type AuthorizationResponse, type AuthorizedHandler, type CsrfToken, type DBAdapter, DBIndexedSessionRepository, type KVRepository, MAX_NAME_PART_LENGTH, type Namespace, type NativeCredential, type OAuth2AuthorizationOptions, OAuth2Client, OAuth2Error, type OAuth2ErrorType, type OAuth2Token, OIDC_SCOPES, type OidcScope, type OidcToken, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, type PersonNameComponents, type Principal, type Provider, RedisIndexedSessionRepository, RedisKVRepository, type Session, type SessionRepository, type StandardClaims, type UserInfo, composeName, oauth2RedirectQuerySchema };
package/dist/index.d.mts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { CsrfToken, PROVIDERS, Principal, Provider } from "./core/index.mjs";
2
2
  import { KVRepository, Namespace, Session, SessionRepository } from "./session/types.mjs";
3
- import { NativeCredential, OAuth2Token, OIDC_SCOPES, OidcScope, OidcToken, StandardClaims, UserInfo } from "./oauth2/types.mjs";
4
- import { AuthConfig, AuthService } from "./auth/types.mjs";
3
+ import { AuthorizationResponse, NativeCredential, OAuth2Token, OIDC_SCOPES, OidcScope, OidcToken, PersonNameComponents, StandardClaims, UserInfo } from "./oauth2/types.mjs";
4
+ import { AuthConfig, AuthService, Authorization, AuthorizedHandler, OAuth2AuthorizationOptions } from "./auth/types.mjs";
5
5
  import { Auth, PATH } from "./auth/index.mjs";
6
6
  import { OAuth2Client, oauth2RedirectQuerySchema } from "./oauth2/client.mjs";
7
7
  import { OAuth2Error, OAuth2ErrorType } from "./oauth2/error.mjs";
8
8
  import { RedisIndexedSessionRepository, RedisKVRepository } from "./session/redis.mjs";
9
9
  import { DBAdapter, DBIndexedSessionRepository } from "./session/db.mjs";
10
10
  import { PRINCIPAL_NAME_INDEX_NAME } from "./session/common.mjs";
11
- export { Auth, type AuthConfig, type AuthService, type CsrfToken, type DBAdapter, DBIndexedSessionRepository, type KVRepository, type Namespace, type NativeCredential, OAuth2Client, OAuth2Error, type OAuth2ErrorType, type OAuth2Token, OIDC_SCOPES, type OidcScope, type OidcToken, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, type Principal, type Provider, RedisIndexedSessionRepository, RedisKVRepository, type Session, type SessionRepository, type StandardClaims, type UserInfo, oauth2RedirectQuerySchema };
11
+ import { MAX_NAME_PART_LENGTH, composeName } from "./oauth2/name.mjs";
12
+ export { Auth, type AuthConfig, type AuthService, type Authorization, type AuthorizationResponse, type AuthorizedHandler, type CsrfToken, type DBAdapter, DBIndexedSessionRepository, type KVRepository, MAX_NAME_PART_LENGTH, type Namespace, type NativeCredential, type OAuth2AuthorizationOptions, OAuth2Client, OAuth2Error, type OAuth2ErrorType, type OAuth2Token, OIDC_SCOPES, type OidcScope, type OidcToken, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, type PersonNameComponents, type Principal, type Provider, RedisIndexedSessionRepository, RedisKVRepository, type Session, type SessionRepository, type StandardClaims, type UserInfo, composeName, oauth2RedirectQuerySchema };
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { OAuth2Client, oauth2RedirectQuerySchema } from "./oauth2/client.mjs";
2
2
  import { OAuth2Error } from "./oauth2/error.mjs";
3
+ import { MAX_NAME_PART_LENGTH, composeName } from "./oauth2/name.mjs";
3
4
  import { PRINCIPAL_NAME_INDEX_NAME } from "./session/common.mjs";
4
5
  import { Auth, PATH } from "./auth/index.mjs";
5
6
  import { RedisIndexedSessionRepository, RedisKVRepository } from "./session/redis.mjs";
6
7
  import { DBIndexedSessionRepository } from "./session/db.mjs";
7
8
  import { PROVIDERS } from "./core/index.mjs";
8
9
  import { OIDC_SCOPES } from "./oauth2/types.mjs";
9
- export { Auth, DBIndexedSessionRepository, OAuth2Client, OAuth2Error, OIDC_SCOPES, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, RedisIndexedSessionRepository, RedisKVRepository, oauth2RedirectQuerySchema };
10
+ export { Auth, DBIndexedSessionRepository, MAX_NAME_PART_LENGTH, OAuth2Client, OAuth2Error, OIDC_SCOPES, PATH, PRINCIPAL_NAME_INDEX_NAME, PROVIDERS, RedisIndexedSessionRepository, RedisKVRepository, composeName, oauth2RedirectQuerySchema };
@@ -7,7 +7,8 @@ const oauth2RedirectQuerySchema = (0, zod_v4_mini.object)({
7
7
  state: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)()),
8
8
  error: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)()),
9
9
  error_description: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)()),
10
- error_uri: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)())
10
+ error_uri: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)()),
11
+ user: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)())
11
12
  });
12
13
  const googleOneTapSchema = (0, zod_v4_mini.object)({
13
14
  code: (0, zod_v4_mini.optional)((0, zod_v4_mini.string)()),
@@ -68,9 +69,9 @@ var OAuth2Client = class {
68
69
  redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`
69
70
  });
70
71
  }
71
- async getUserInfo({ registrationId, token }) {
72
+ async getUserInfo({ registrationId, token, response }) {
72
73
  const { provider } = await this.getClientConfig(registrationId);
73
- return provider.getUserInfo(token);
74
+ return provider.getUserInfo(token, response);
74
75
  }
75
76
  async refreshAccessToken({ registrationId, refreshToken }) {
76
77
  const { provider, registration } = await this.getClientConfig(registrationId);
@@ -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":"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"}
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 AuthorizationResponse,\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 // 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 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 response,\n }: {\n registrationId: string;\n token: OAuth2Token | OidcToken;\n response?: AuthorizationResponse;\n }) {\n const { provider } = await this.getClientConfig(registrationId);\n return provider.getUserInfo(token, response);\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":";;;;AAWA,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;CAE5B,OAAA,GAAMD,YAAAA,SAAAA,EAAAA,GAASC,YAAAA,OAAAA,CAAO,CAAC;AACzB,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,OACA,YAKC;EACD,MAAM,EAAE,aAAa,MAAM,KAAK,gBAAgB,cAAc;EAC9D,OAAO,SAAS,YAAY,OAAO,QAAQ;CAC7C;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,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>>;
@@ -35,9 +36,10 @@ export declare class OAuth2Client {
35
36
  code: string;
36
37
  pkce?: PkceParameters;
37
38
  }): Promise<OAuth2Token>;
38
- getUserInfo({ registrationId, token }: {
39
+ getUserInfo({ registrationId, token, response }: {
39
40
  registrationId: string;
40
41
  token: OAuth2Token | OidcToken;
42
+ response?: AuthorizationResponse;
41
43
  }): Promise<UserInfo<unknown>>;
42
44
  refreshAccessToken({ registrationId, refreshToken }: {
43
45
  registrationId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.cts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAUa,8CAAyB;;;;;;yBAMpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;UAIU;EAcR,yBACJ,gBACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,4BACJ,gBACA,MACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,cACJ,gBACA;IAEA;IACA,OAAO,cAAc;MACtB,QADsBA;EAMjB,qBACJ,gBACA;IAEA;IACA;MACE,QAAQ;EAQN,YAAY,wBAAwB,gBAAa;EAQjD,oBACJ,gBACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
1
+ {"version":3,"file":"client.d.cts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAWa,8CAAyB;;;;;;;yBAQpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;UAIU;EAcR,yBACJ,gBACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,4BACJ,gBACA,MACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,cACJ,gBACA,OACA;IAEA;IACA,OAAO,cAAc;IACrB,WAAW;MACZ,QADYA;EAMP,qBACJ,gBACA;IAEA;IACA;MACE,QAAQ;EAQN,YAAY,wBAAwB,gBAAa;EAQjD,oBACJ,gBACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
@@ -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>>;
@@ -35,9 +36,10 @@ export declare class OAuth2Client {
35
36
  code: string;
36
37
  pkce?: PkceParameters;
37
38
  }): Promise<OAuth2Token>;
38
- getUserInfo({ registrationId, token }: {
39
+ getUserInfo({ registrationId, token, response }: {
39
40
  registrationId: string;
40
41
  token: OAuth2Token | OidcToken;
42
+ response?: AuthorizationResponse;
41
43
  }): Promise<UserInfo<unknown>>;
42
44
  refreshAccessToken({ registrationId, refreshToken }: {
43
45
  registrationId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAUa,8CAAyB;;;;;;yBAMpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;UAIU;EAcR,yBACJ,gBACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,4BACJ,gBACA,MACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,cACJ,gBACA;IAEA;IACA,OAAO,cAAc;MACtB,QADsBA;EAMjB,qBACJ,gBACA;IAEA;IACA;MACE,QAAQ;EAQN,YAAY,wBAAwB,gBAAa;EAQjD,oBACJ,gBACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
1
+ {"version":3,"file":"client.d.mts","names":["_$__types0.UserInfo"],"sources":["../../src/oauth2/client.ts"],"mappings":";;qBAWa,8CAAyB;;;;;;;yBAQpC;qBAEW,uCAAkB;;;;;;;;;;yBAY7B;qBAEW;mBACM;EACjB,YAAY,QAAQ;MAIhB;MAIA;MAIA;UAIU;EAcR,yBACJ,gBACA,OACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,4BACJ,gBACA,MACA;IAEA;IACA;IACA,OAAO;MACL,QAAQ;EAcN,cACJ,gBACA,OACA;IAEA;IACA,OAAO,cAAc;IACrB,WAAW;MACZ,QADYA;EAMP,qBACJ,gBACA;IAEA;IACA;MACE,QAAQ;EAQN,YAAY,wBAAwB,gBAAa;EAQjD,oBACJ,gBACA,aACA;IAEA;IACA,aAAa;IACb,OAAO;MACR"}
@@ -6,7 +6,8 @@ const oauth2RedirectQuerySchema = object({
6
6
  state: optional(string()),
7
7
  error: optional(string()),
8
8
  error_description: optional(string()),
9
- error_uri: optional(string())
9
+ error_uri: optional(string()),
10
+ user: optional(string())
10
11
  });
11
12
  const googleOneTapSchema = object({
12
13
  code: optional(string()),
@@ -67,9 +68,9 @@ var OAuth2Client = class {
67
68
  redirectUri: redirectUri ?? `${baseUri}/login/oauth2/code/${registrationId}`
68
69
  });
69
70
  }
70
- async getUserInfo({ registrationId, token }) {
71
+ async getUserInfo({ registrationId, token, response }) {
71
72
  const { provider } = await this.getClientConfig(registrationId);
72
- return provider.getUserInfo(token);
73
+ return provider.getUserInfo(token, response);
73
74
  }
74
75
  async refreshAccessToken({ registrationId, refreshToken }) {
75
76
  const { provider, registration } = await this.getClientConfig(registrationId);
@@ -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":"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"}
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 AuthorizationResponse,\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 // 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 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 response,\n }: {\n registrationId: string;\n token: OAuth2Token | OidcToken;\n response?: AuthorizationResponse;\n }) {\n const { provider } = await this.getClientConfig(registrationId);\n return provider.getUserInfo(token, response);\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":"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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"}
@@ -0,0 +1,49 @@
1
+ import { PersonNameComponents, StandardClaims } from "./types.cjs";
2
+ //#region src/oauth2/name.d.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
+ export declare const MAX_NAME_PART_LENGTH = 64;
9
+ /**
10
+ * Composes a display `name` from its parts, for providers that hand out the parts only. Chinese,
11
+ * Japanese and Korean names are written family name first without a space (`王小明`), every other
12
+ * name given name first (`John Appleseed`), which is also the safe choice for mixed scripts. With
13
+ * one part only, that part is the name.
14
+ */
15
+ export declare function composeName({ given_name, family_name }: Pick<StandardClaims, 'given_name' | 'family_name'>): string | undefined;
16
+ /**
17
+ * One untrusted name part: a non-blank string, trimmed and capped at `MAX_NAME_PART_LENGTH`
18
+ * characters (code points, so a cut never splits one). Anything else is dropped rather than refused,
19
+ * because the name is a nicety and must never cost the user their login.
20
+ */
21
+ export declare function cleanNamePart(value: unknown): string | undefined;
22
+ /** Name parts as Apple's form_post `user` carries them, after cleaning. */
23
+ export interface AppleUser {
24
+ name?: {
25
+ firstName?: string;
26
+ lastName?: string;
27
+ };
28
+ email?: string;
29
+ }
30
+ /**
31
+ * Parses Apple's form_post `user` (JSON, first authorization only). Malformed JSON or a wrong shape
32
+ * yields `undefined`, never an error. Only known fields are kept, each cleaned, so what a host
33
+ * persists stays small.
34
+ */
35
+ export declare function parseAppleUser(raw: string | undefined): AppleUser | undefined;
36
+ /**
37
+ * Cleans the `fullName` of a native login body (iOS `PersonNameComponents`): known keys only,
38
+ * each a cleaned string; `undefined` when nothing usable is left.
39
+ */
40
+ export declare function cleanPersonName(value: unknown): PersonNameComponents | undefined;
41
+ type NameClaims = Pick<StandardClaims, 'name' | 'given_name' | 'family_name' | 'middle_name' | 'nickname'>;
42
+ /**
43
+ * The standard name claims from cleaned parts, with `name` composed from given and family name.
44
+ * Undefined parts are left out, so spreading the result never overwrites a claim with `undefined`.
45
+ */
46
+ export declare function nameClaims(parts: Omit<NameClaims, 'name'>): NameClaims;
47
+ //#endregion
48
+ export {};
49
+ //# sourceMappingURL=name.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.d.cts","names":[],"sources":["../../src/oauth2/name.ts"],"mappings":";;;;;;;qBAOa;;;;;;;wBAaG,cACd,YACA,eACC,KAAK;;;;;;wBAcQ,cAAc;;iBAQb;EACf;IAAS;IAAoB;;EAC7B;;;;;;;wBAQc,eAAe,0BAA0B;;;;;wBAkCzC,gBAAgB,iBAAiB;KAU5C,aAAa,KAChB;;;;;wBAQc,WAAW,OAAO,KAAK,sBAAsB"}
@@ -0,0 +1,49 @@
1
+ import { PersonNameComponents, StandardClaims } from "./types.mjs";
2
+ //#region src/oauth2/name.d.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
+ export declare const MAX_NAME_PART_LENGTH = 64;
9
+ /**
10
+ * Composes a display `name` from its parts, for providers that hand out the parts only. Chinese,
11
+ * Japanese and Korean names are written family name first without a space (`王小明`), every other
12
+ * name given name first (`John Appleseed`), which is also the safe choice for mixed scripts. With
13
+ * one part only, that part is the name.
14
+ */
15
+ export declare function composeName({ given_name, family_name }: Pick<StandardClaims, 'given_name' | 'family_name'>): string | undefined;
16
+ /**
17
+ * One untrusted name part: a non-blank string, trimmed and capped at `MAX_NAME_PART_LENGTH`
18
+ * characters (code points, so a cut never splits one). Anything else is dropped rather than refused,
19
+ * because the name is a nicety and must never cost the user their login.
20
+ */
21
+ export declare function cleanNamePart(value: unknown): string | undefined;
22
+ /** Name parts as Apple's form_post `user` carries them, after cleaning. */
23
+ export interface AppleUser {
24
+ name?: {
25
+ firstName?: string;
26
+ lastName?: string;
27
+ };
28
+ email?: string;
29
+ }
30
+ /**
31
+ * Parses Apple's form_post `user` (JSON, first authorization only). Malformed JSON or a wrong shape
32
+ * yields `undefined`, never an error. Only known fields are kept, each cleaned, so what a host
33
+ * persists stays small.
34
+ */
35
+ export declare function parseAppleUser(raw: string | undefined): AppleUser | undefined;
36
+ /**
37
+ * Cleans the `fullName` of a native login body (iOS `PersonNameComponents`): known keys only,
38
+ * each a cleaned string; `undefined` when nothing usable is left.
39
+ */
40
+ export declare function cleanPersonName(value: unknown): PersonNameComponents | undefined;
41
+ type NameClaims = Pick<StandardClaims, 'name' | 'given_name' | 'family_name' | 'middle_name' | 'nickname'>;
42
+ /**
43
+ * The standard name claims from cleaned parts, with `name` composed from given and family name.
44
+ * Undefined parts are left out, so spreading the result never overwrites a claim with `undefined`.
45
+ */
46
+ export declare function nameClaims(parts: Omit<NameClaims, 'name'>): NameClaims;
47
+ //#endregion
48
+ export {};
49
+ //# sourceMappingURL=name.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.d.mts","names":[],"sources":["../../src/oauth2/name.ts"],"mappings":";;;;;;;qBAOa;;;;;;;wBAaG,cACd,YACA,eACC,KAAK;;;;;;wBAcQ,cAAc;;iBAQb;EACf;IAAS;IAAoB;;EAC7B;;;;;;;wBAQc,eAAe,0BAA0B;;;;;wBAkCzC,gBAAgB,iBAAiB;KAU5C,aAAa,KAChB;;;;;wBAQc,WAAW,OAAO,KAAK,sBAAsB"}
@@ -0,0 +1,101 @@
1
+ //#region src/oauth2/name.ts
2
+ /**
3
+ * Upper bound, in characters, on each name part taken from the client. Names that reach us outside
4
+ * a signed token (Apple's form_post `user`, a native login body) are plain client input, so they
5
+ * are capped before a host stores them; 64 leaves room for any real given or family name.
6
+ */
7
+ const MAX_NAME_PART_LENGTH = 64;
8
+ /** Apple's form_post `user` is a few dozen bytes; anything far larger is not Apple's. */
9
+ const MAX_APPLE_USER_LENGTH = 1024;
10
+ const CJK = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+$/u;
11
+ /**
12
+ * Composes a display `name` from its parts, for providers that hand out the parts only. Chinese,
13
+ * Japanese and Korean names are written family name first without a space (`王小明`), every other
14
+ * name given name first (`John Appleseed`), which is also the safe choice for mixed scripts. With
15
+ * one part only, that part is the name.
16
+ */
17
+ function composeName({ given_name, family_name }) {
18
+ const given = given_name?.trim();
19
+ const family = family_name?.trim();
20
+ if (given && family) return CJK.test(given) && CJK.test(family) ? `${family}${given}` : `${given} ${family}`;
21
+ return given || family || void 0;
22
+ }
23
+ /**
24
+ * One untrusted name part: a non-blank string, trimmed and capped at `MAX_NAME_PART_LENGTH`
25
+ * characters (code points, so a cut never splits one). Anything else is dropped rather than refused,
26
+ * because the name is a nicety and must never cost the user their login.
27
+ */
28
+ function cleanNamePart(value) {
29
+ if (typeof value !== "string") return void 0;
30
+ const trimmed = value.trim();
31
+ if (!trimmed) return void 0;
32
+ return Array.from(trimmed).slice(0, 64).join("");
33
+ }
34
+ /**
35
+ * Parses Apple's form_post `user` (JSON, first authorization only). Malformed JSON or a wrong shape
36
+ * yields `undefined`, never an error. Only known fields are kept, each cleaned, so what a host
37
+ * persists stays small.
38
+ */
39
+ function parseAppleUser(raw) {
40
+ if (!raw || raw.length > MAX_APPLE_USER_LENGTH) return void 0;
41
+ let json;
42
+ try {
43
+ json = JSON.parse(raw);
44
+ } catch {
45
+ return;
46
+ }
47
+ if (!isObject(json)) return void 0;
48
+ const user = {};
49
+ if (isObject(json.name)) {
50
+ const firstName = cleanNamePart(json.name.firstName);
51
+ const lastName = cleanNamePart(json.name.lastName);
52
+ if (firstName || lastName) user.name = withoutUndefined({
53
+ firstName,
54
+ lastName
55
+ });
56
+ }
57
+ const email = typeof json.email === "string" ? json.email.trim().slice(0, 320) : void 0;
58
+ if (email) user.email = email;
59
+ return user.name || user.email ? user : void 0;
60
+ }
61
+ const PERSON_NAME_KEYS = [
62
+ "givenName",
63
+ "familyName",
64
+ "middleName",
65
+ "namePrefix",
66
+ "nameSuffix",
67
+ "nickname"
68
+ ];
69
+ /**
70
+ * Cleans the `fullName` of a native login body (iOS `PersonNameComponents`): known keys only,
71
+ * each a cleaned string; `undefined` when nothing usable is left.
72
+ */
73
+ function cleanPersonName(value) {
74
+ if (!isObject(value)) return void 0;
75
+ const name = {};
76
+ for (const key of PERSON_NAME_KEYS) {
77
+ const part = cleanNamePart(value[key]);
78
+ if (part) name[key] = part;
79
+ }
80
+ return Object.keys(name).length > 0 ? name : void 0;
81
+ }
82
+ /**
83
+ * The standard name claims from cleaned parts, with `name` composed from given and family name.
84
+ * Undefined parts are left out, so spreading the result never overwrites a claim with `undefined`.
85
+ */
86
+ function nameClaims(parts) {
87
+ return withoutUndefined({
88
+ ...parts,
89
+ name: composeName(parts)
90
+ });
91
+ }
92
+ function isObject(value) {
93
+ return typeof value === "object" && value !== null && !Array.isArray(value);
94
+ }
95
+ function withoutUndefined(value) {
96
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
97
+ }
98
+ //#endregion
99
+ export { MAX_NAME_PART_LENGTH, cleanNamePart, cleanPersonName, composeName, nameClaims, parseAppleUser };
100
+
101
+ //# sourceMappingURL=name.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.mjs","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"}