@shware/security 7.1.1 → 8.0.1

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 (132) hide show
  1. package/README.md +113 -32
  2. package/dist/auth/attributes.cjs +4 -3
  3. package/dist/auth/attributes.cjs.map +1 -1
  4. package/dist/auth/attributes.d.cts +3 -3
  5. package/dist/auth/attributes.d.cts.map +1 -1
  6. package/dist/auth/attributes.d.mts +3 -3
  7. package/dist/auth/attributes.d.mts.map +1 -1
  8. package/dist/auth/attributes.mjs +4 -3
  9. package/dist/auth/attributes.mjs.map +1 -1
  10. package/dist/auth/desktop-codes.cjs +57 -0
  11. package/dist/auth/desktop-codes.cjs.map +1 -0
  12. package/dist/auth/desktop-codes.d.cts +24 -0
  13. package/dist/auth/desktop-codes.d.cts.map +1 -0
  14. package/dist/auth/desktop-codes.d.mts +24 -0
  15. package/dist/auth/desktop-codes.d.mts.map +1 -0
  16. package/dist/auth/desktop-codes.mjs +55 -0
  17. package/dist/auth/desktop-codes.mjs.map +1 -0
  18. package/dist/auth/email-codes.cjs +77 -0
  19. package/dist/auth/email-codes.cjs.map +1 -0
  20. package/dist/auth/email-codes.d.cts +39 -0
  21. package/dist/auth/email-codes.d.cts.map +1 -0
  22. package/dist/auth/email-codes.d.mts +39 -0
  23. package/dist/auth/email-codes.d.mts.map +1 -0
  24. package/dist/auth/email-codes.mjs +75 -0
  25. package/dist/auth/email-codes.mjs.map +1 -0
  26. package/dist/auth/error.cjs +47 -0
  27. package/dist/auth/error.cjs.map +1 -0
  28. package/dist/auth/error.d.cts +88 -0
  29. package/dist/auth/error.d.cts.map +1 -0
  30. package/dist/auth/error.d.mts +88 -0
  31. package/dist/auth/error.d.mts.map +1 -0
  32. package/dist/auth/error.mjs +45 -0
  33. package/dist/auth/error.mjs.map +1 -0
  34. package/dist/auth/index.cjs +249 -384
  35. package/dist/auth/index.cjs.map +1 -1
  36. package/dist/auth/index.d.cts +66 -55
  37. package/dist/auth/index.d.cts.map +1 -1
  38. package/dist/auth/index.d.mts +66 -55
  39. package/dist/auth/index.d.mts.map +1 -1
  40. package/dist/auth/index.mjs +252 -387
  41. package/dist/auth/index.mjs.map +1 -1
  42. package/dist/auth/respond.cjs +99 -0
  43. package/dist/auth/respond.cjs.map +1 -0
  44. package/dist/auth/respond.d.cts +39 -0
  45. package/dist/auth/respond.d.cts.map +1 -0
  46. package/dist/auth/respond.d.mts +39 -0
  47. package/dist/auth/respond.d.mts.map +1 -0
  48. package/dist/auth/respond.mjs +92 -0
  49. package/dist/auth/respond.mjs.map +1 -0
  50. package/dist/auth/types.d.cts +27 -11
  51. package/dist/auth/types.d.cts.map +1 -1
  52. package/dist/auth/types.d.mts +27 -11
  53. package/dist/auth/types.d.mts.map +1 -1
  54. package/dist/index.cjs +5 -0
  55. package/dist/index.d.cts +4 -1
  56. package/dist/index.d.mts +4 -1
  57. package/dist/index.mjs +3 -1
  58. package/dist/oauth2/error.cjs +0 -7
  59. package/dist/oauth2/error.cjs.map +1 -1
  60. package/dist/oauth2/error.d.cts +0 -3
  61. package/dist/oauth2/error.d.cts.map +1 -1
  62. package/dist/oauth2/error.d.mts +0 -3
  63. package/dist/oauth2/error.d.mts.map +1 -1
  64. package/dist/oauth2/error.mjs +1 -7
  65. package/dist/oauth2/error.mjs.map +1 -1
  66. package/dist/oauth2/provider/common.cjs +17 -3
  67. package/dist/oauth2/provider/common.cjs.map +1 -1
  68. package/dist/oauth2/provider/common.d.cts.map +1 -1
  69. package/dist/oauth2/provider/common.d.mts.map +1 -1
  70. package/dist/oauth2/provider/common.mjs +17 -3
  71. package/dist/oauth2/provider/common.mjs.map +1 -1
  72. package/dist/oauth2/provider/facebook.cjs +2 -4
  73. package/dist/oauth2/provider/facebook.cjs.map +1 -1
  74. package/dist/oauth2/provider/facebook.d.cts +0 -1
  75. package/dist/oauth2/provider/facebook.d.cts.map +1 -1
  76. package/dist/oauth2/provider/facebook.d.mts +0 -1
  77. package/dist/oauth2/provider/facebook.d.mts.map +1 -1
  78. package/dist/oauth2/provider/facebook.mjs +2 -4
  79. package/dist/oauth2/provider/facebook.mjs.map +1 -1
  80. package/dist/oauth2/provider/github.cjs +1 -1
  81. package/dist/oauth2/provider/github.cjs.map +1 -1
  82. package/dist/oauth2/provider/github.d.cts +1 -1
  83. package/dist/oauth2/provider/github.d.cts.map +1 -1
  84. package/dist/oauth2/provider/github.d.mts +1 -1
  85. package/dist/oauth2/provider/github.d.mts.map +1 -1
  86. package/dist/oauth2/provider/github.mjs +1 -1
  87. package/dist/oauth2/provider/github.mjs.map +1 -1
  88. package/dist/oauth2/provider/x.cjs +6 -4
  89. package/dist/oauth2/provider/x.cjs.map +1 -1
  90. package/dist/oauth2/provider/x.d.cts +3 -2
  91. package/dist/oauth2/provider/x.d.cts.map +1 -1
  92. package/dist/oauth2/provider/x.d.mts +3 -2
  93. package/dist/oauth2/provider/x.d.mts.map +1 -1
  94. package/dist/oauth2/provider/x.mjs +6 -4
  95. package/dist/oauth2/provider/x.mjs.map +1 -1
  96. package/dist/oauth2/types.cjs.map +1 -1
  97. package/dist/oauth2/types.d.cts +5 -0
  98. package/dist/oauth2/types.d.cts.map +1 -1
  99. package/dist/oauth2/types.d.mts +5 -0
  100. package/dist/oauth2/types.d.mts.map +1 -1
  101. package/dist/oauth2/types.mjs.map +1 -1
  102. package/dist/session/common.cjs +10 -13
  103. package/dist/session/common.cjs.map +1 -1
  104. package/dist/session/common.d.cts +7 -3
  105. package/dist/session/common.d.cts.map +1 -1
  106. package/dist/session/common.d.mts +7 -3
  107. package/dist/session/common.d.mts.map +1 -1
  108. package/dist/session/common.mjs +10 -12
  109. package/dist/session/common.mjs.map +1 -1
  110. package/dist/session/db.cjs +2 -2
  111. package/dist/session/db.cjs.map +1 -1
  112. package/dist/session/db.mjs +3 -3
  113. package/dist/session/db.mjs.map +1 -1
  114. package/dist/session/redis.cjs +6 -8
  115. package/dist/session/redis.cjs.map +1 -1
  116. package/dist/session/redis.d.cts.map +1 -1
  117. package/dist/session/redis.d.mts.map +1 -1
  118. package/dist/session/redis.mjs +7 -9
  119. package/dist/session/redis.mjs.map +1 -1
  120. package/dist/session/types.d.cts +0 -12
  121. package/dist/session/types.d.cts.map +1 -1
  122. package/dist/session/types.d.mts +0 -12
  123. package/dist/session/types.d.mts.map +1 -1
  124. package/dist/utils/valid.cjs +34 -52
  125. package/dist/utils/valid.cjs.map +1 -1
  126. package/dist/utils/valid.d.cts +8 -23
  127. package/dist/utils/valid.d.cts.map +1 -1
  128. package/dist/utils/valid.d.mts +8 -23
  129. package/dist/utils/valid.d.mts.map +1 -1
  130. package/dist/utils/valid.mjs +32 -50
  131. package/dist/utils/valid.mjs.map +1 -1
  132. package/package.json +3 -3
@@ -52,8 +52,7 @@ function createFacebookProvider() {
52
52
  sub: data.sub,
53
53
  name: data.name,
54
54
  picture: data.picture,
55
- email: data.email,
56
- email_verified: true
55
+ email: data.email
57
56
  }
58
57
  };
59
58
  }
@@ -107,8 +106,7 @@ async function fetchUserInfo(userInfoUri, accessToken, appSecretProof) {
107
106
  sub: data.id,
108
107
  name: data.name,
109
108
  email: data.email,
110
- picture: data.picture.data.url,
111
- email_verified: data.email_verified
109
+ picture: data.picture.data.url
112
110
  }
113
111
  };
114
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"facebook.mjs","names":[],"sources":["../../../src/oauth2/provider/facebook.ts"],"sourcesContent":["import { createHmac } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { LoginOAuth2NativeParams, OAuth2Token, Provider, UserInfo } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/**\n * `iss` of Limited Login id tokens, per https://limited.facebook.com/.well-known/openid-configuration/\n * (their `aud` is the app id).\n */\nconst FACEBOOK_ISSUER = 'https://www.facebook.com';\n\n/**\n * ref: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/\n * ref: https://developers.facebook.com/tools/explorer\n * */\nexport function createFacebookProvider(): Provider {\n return {\n authorizationUri: 'https://www.facebook.com/v21.0/dialog/oauth',\n tokenUri: 'https://graph.facebook.com/v19.0/oauth/access_token',\n userInfoUri: 'https://graph.facebook.com/me',\n jwkSetUri: 'https://limited.facebook.com/.well-known/oauth/openid/jwks/',\n userNameAttribute: 'id',\n defaultScope: ['profile', 'email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error } = (await response.json()) as FacebookErrorResponse;\n throw new OAuth2Error(response.status, 'invalid_request', error.message);\n }\n return (await response.json()) as FacebookToken;\n },\n async getUserInfo({ id_token, access_token }, { clientId }) {\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n if (id_token) {\n const data = await verifyIdToken<FacebookDecodedIdToken>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: FACEBOOK_ISSUER,\n audience: clientId,\n });\n return {\n data,\n claims: {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n email_verified: true,\n },\n };\n }\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n return fetchUserInfo(this.userInfoUri, access_token);\n },\n async loginOAuth2Native({ clientId, clientSecret, credentials }: LoginOAuth2NativeParams) {\n const { id_token, access_token } = credentials;\n let userInfo: UserInfo;\n if (id_token) {\n // Limited Login: the id token's `aud` must be this app.\n userInfo = await this.getUserInfo(\n { token_type: 'bearer', access_token: '', id_token },\n {\n clientId,\n }\n );\n } else if (access_token) {\n // The Graph API serves any app's user access token, so `/me` alone would accept a token a\n // foreign app obtained for the same user. With `appsecret_proof` Facebook refuses tokens\n // not issued to the app whose secret signed the proof, binding the token to this client.\n if (!clientSecret) {\n throw new OAuth2Error(400, 'invalid_client', 'access_token login needs the app secret');\n }\n invariant(this.userInfoUri, 'userInfoUri is required');\n const proof = createHmac('sha256', clientSecret).update(access_token).digest('hex');\n userInfo = await fetchUserInfo(this.userInfoUri, access_token, proof);\n } else {\n throw new OAuth2Error(400, 'invalid_request', 'id_token or access_token is required');\n }\n const token: OAuth2Token = {\n token_type: 'bearer',\n access_token: access_token ?? '',\n id_token,\n };\n return { token, userInfo };\n },\n };\n}\n\nexport const facebook = createFacebookProvider();\n\nasync function fetchUserInfo(\n userInfoUri: string,\n accessToken: string,\n appSecretProof?: string\n): Promise<UserInfo<FacebookUserInfo>> {\n const url = new URL(userInfoUri);\n const fields = ['id', 'name', 'email', 'picture.width(320).height(320)'];\n url.searchParams.append('access_token', accessToken);\n url.searchParams.append('fields', fields.join(','));\n if (appSecretProof) url.searchParams.append('appsecret_proof', appSecretProof);\n const headers = { Accept: 'application/json' };\n const response = await fetch(url.href, { headers });\n if (!response.ok) {\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to fetch user info');\n }\n const data = (await response.json()) as FacebookUserInfo;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name,\n email: data.email,\n picture: data.picture.data.url,\n email_verified: data.email_verified,\n },\n };\n}\n\nexport interface FacebookDecodedIdToken {\n sub: string;\n name: string;\n email: string;\n picture: string;\n nonce?: string;\n}\n\nexport interface FacebookUserInfo {\n id: string;\n name: string;\n email: string;\n email_verified: boolean;\n picture: {\n data: {\n url: string;\n width: number;\n height: number;\n is_silhouette: boolean;\n };\n };\n}\n\nexport interface FacebookToken extends OAuth2Token {\n access_token: string;\n token_type: 'bearer';\n expires_in: number;\n id_token?: string;\n}\n\nexport type FacebookAppCredential =\n | { state: string; id_token: string } // platform=ios and tracking=limited\n | { state: string; access_token: string }; // tracking=enabled\n\ninterface FacebookErrorResponse {\n error: {\n message: string;\n type: 'OAuthException';\n code: number;\n error_subcode: number;\n fbtrace_id: string;\n };\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,kBAAkB;;;;;AAMxB,SAAgB,yBAAmC;CACjD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,WAAW;EACX,mBAAmB;EACnB,cAAc,CAAC,WAAW,OAAO;EACjC,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,UAAW,MAAM,SAAS,KAAK;IACvC,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,MAAM,OAAO;GACzE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,UAAU,gBAAgB,EAAE,YAAY;GAC1D,UAAU,KAAK,WAAW,uBAAuB;GACjD,IAAI,UAAU;IACZ,MAAM,OAAO,MAAM,cAAsC,UAAU;KACjE,WAAW,KAAK;KAChB,QAAQ;KACR,UAAU;IACZ,CAAC;IACD,OAAO;KACL;KACA,QAAQ;MACN,KAAK,KAAK;MACV,MAAM,KAAK;MACX,SAAS,KAAK;MACd,OAAO,KAAK;MACZ,gBAAgB;KAClB;IACF;GACF;GACA,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,OAAO,cAAc,KAAK,aAAa,YAAY;EACrD;EACA,MAAM,kBAAkB,EAAE,UAAU,cAAc,eAAwC;GACxF,MAAM,EAAE,UAAU,iBAAiB;GACnC,IAAI;GACJ,IAAI,UAEF,WAAW,MAAM,KAAK,YACpB;IAAE,YAAY;IAAU,cAAc;IAAI;GAAS,GACnD,EACE,SACF,CACF;QACK,IAAI,cAAc;IAIvB,IAAI,CAAC,cACH,MAAM,IAAI,YAAY,KAAK,kBAAkB,yCAAyC;IAExF,UAAU,KAAK,aAAa,yBAAyB;IACrD,MAAM,QAAQ,WAAW,UAAU,YAAY,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,OAAO,KAAK;IAClF,WAAW,MAAM,cAAc,KAAK,aAAa,cAAc,KAAK;GACtE,OACE,MAAM,IAAI,YAAY,KAAK,mBAAmB,sCAAsC;GAOtF,OAAO;IAAE,OAAA;KAJP,YAAY;KACZ,cAAc,gBAAgB;KAC9B;IAEW;IAAG;GAAS;EAC3B;CACF;AACF;AAEA,MAAa,WAAW,uBAAuB;AAE/C,eAAe,cACb,aACA,aACA,gBACqC;CACrC,MAAM,MAAM,IAAI,IAAI,WAAW;CAC/B,MAAM,SAAS;EAAC;EAAM;EAAQ;EAAS;CAAgC;CACvE,IAAI,aAAa,OAAO,gBAAgB,WAAW;CACnD,IAAI,aAAa,OAAO,UAAU,OAAO,KAAK,GAAG,CAAC;CAClD,IAAI,gBAAgB,IAAI,aAAa,OAAO,mBAAmB,cAAc;CAE7E,MAAM,WAAW,MAAM,MAAM,IAAI,MAAM,EAAE,SAAA,EADvB,QAAQ,mBACqB,EAAE,CAAC;CAClD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,2BAA2B;CAEvF,MAAM,OAAQ,MAAM,SAAS,KAAK;CAClC,OAAO;EACL;EACA,QAAQ;GACN,KAAK,KAAK;GACV,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,SAAS,KAAK,QAAQ,KAAK;GAC3B,gBAAgB,KAAK;EACvB;CACF;AACF"}
1
+ {"version":3,"file":"facebook.mjs","names":[],"sources":["../../../src/oauth2/provider/facebook.ts"],"sourcesContent":["import { createHmac } from 'node:crypto';\nimport { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { LoginOAuth2NativeParams, OAuth2Token, Provider, UserInfo } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/**\n * `iss` of Limited Login id tokens, per https://limited.facebook.com/.well-known/openid-configuration/\n * (their `aud` is the app id).\n */\nconst FACEBOOK_ISSUER = 'https://www.facebook.com';\n\n/**\n * ref: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/\n * ref: https://developers.facebook.com/tools/explorer\n * */\nexport function createFacebookProvider(): Provider {\n return {\n authorizationUri: 'https://www.facebook.com/v21.0/dialog/oauth',\n tokenUri: 'https://graph.facebook.com/v19.0/oauth/access_token',\n userInfoUri: 'https://graph.facebook.com/me',\n jwkSetUri: 'https://limited.facebook.com/.well-known/oauth/openid/jwks/',\n userNameAttribute: 'id',\n defaultScope: ['profile', 'email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error } = (await response.json()) as FacebookErrorResponse;\n throw new OAuth2Error(response.status, 'invalid_request', error.message);\n }\n return (await response.json()) as FacebookToken;\n },\n async getUserInfo({ id_token, access_token }, { clientId }) {\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n if (id_token) {\n const data = await verifyIdToken<FacebookDecodedIdToken>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: FACEBOOK_ISSUER,\n audience: clientId,\n });\n return {\n data,\n claims: {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n // Facebook does not say whether the address is verified: left unset, so a host that\n // trusts only `email_verified === true` treats it as unverified.\n },\n };\n }\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n return fetchUserInfo(this.userInfoUri, access_token);\n },\n async loginOAuth2Native({ clientId, clientSecret, credentials }: LoginOAuth2NativeParams) {\n const { id_token, access_token } = credentials;\n let userInfo: UserInfo;\n if (id_token) {\n // Limited Login: the id token's `aud` must be this app.\n userInfo = await this.getUserInfo(\n { token_type: 'bearer', access_token: '', id_token },\n {\n clientId,\n }\n );\n } else if (access_token) {\n // The Graph API serves any app's user access token, so `/me` alone would accept a token a\n // foreign app obtained for the same user. With `appsecret_proof` Facebook refuses tokens\n // not issued to the app whose secret signed the proof, binding the token to this client.\n if (!clientSecret) {\n throw new OAuth2Error(400, 'invalid_client', 'access_token login needs the app secret');\n }\n invariant(this.userInfoUri, 'userInfoUri is required');\n const proof = createHmac('sha256', clientSecret).update(access_token).digest('hex');\n userInfo = await fetchUserInfo(this.userInfoUri, access_token, proof);\n } else {\n throw new OAuth2Error(400, 'invalid_request', 'id_token or access_token is required');\n }\n const token: OAuth2Token = {\n token_type: 'bearer',\n access_token: access_token ?? '',\n id_token,\n };\n return { token, userInfo };\n },\n };\n}\n\nexport const facebook = createFacebookProvider();\n\nasync function fetchUserInfo(\n userInfoUri: string,\n accessToken: string,\n appSecretProof?: string\n): Promise<UserInfo<FacebookUserInfo>> {\n const url = new URL(userInfoUri);\n const fields = ['id', 'name', 'email', 'picture.width(320).height(320)'];\n url.searchParams.append('access_token', accessToken);\n url.searchParams.append('fields', fields.join(','));\n if (appSecretProof) url.searchParams.append('appsecret_proof', appSecretProof);\n const headers = { Accept: 'application/json' };\n const response = await fetch(url.href, { headers });\n if (!response.ok) {\n throw new OAuth2Error(response.status, 'invalid_request', 'Failed to fetch user info');\n }\n const data = (await response.json()) as FacebookUserInfo;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name,\n email: data.email,\n picture: data.picture.data.url,\n // Not stated by the Graph API either; see the Limited Login path above.\n },\n };\n}\n\nexport interface FacebookDecodedIdToken {\n sub: string;\n name: string;\n email: string;\n picture: string;\n nonce?: string;\n}\n\nexport interface FacebookUserInfo {\n id: string;\n name: string;\n email: string;\n picture: {\n data: {\n url: string;\n width: number;\n height: number;\n is_silhouette: boolean;\n };\n };\n}\n\nexport interface FacebookToken extends OAuth2Token {\n access_token: string;\n token_type: 'bearer';\n expires_in: number;\n id_token?: string;\n}\n\nexport type FacebookAppCredential =\n | { state: string; id_token: string } // platform=ios and tracking=limited\n | { state: string; access_token: string }; // tracking=enabled\n\ninterface FacebookErrorResponse {\n error: {\n message: string;\n type: 'OAuthException';\n code: number;\n error_subcode: number;\n fbtrace_id: string;\n };\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,kBAAkB;;;;;AAMxB,SAAgB,yBAAmC;CACjD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,WAAW;EACX,mBAAmB;EACnB,cAAc,CAAC,WAAW,OAAO;EACjC,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,UAAW,MAAM,SAAS,KAAK;IACvC,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,MAAM,OAAO;GACzE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,UAAU,gBAAgB,EAAE,YAAY;GAC1D,UAAU,KAAK,WAAW,uBAAuB;GACjD,IAAI,UAAU;IACZ,MAAM,OAAO,MAAM,cAAsC,UAAU;KACjE,WAAW,KAAK;KAChB,QAAQ;KACR,UAAU;IACZ,CAAC;IACD,OAAO;KACL;KACA,QAAQ;MACN,KAAK,KAAK;MACV,MAAM,KAAK;MACX,SAAS,KAAK;MACd,OAAO,KAAK;KAGd;IACF;GACF;GACA,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,OAAO,cAAc,KAAK,aAAa,YAAY;EACrD;EACA,MAAM,kBAAkB,EAAE,UAAU,cAAc,eAAwC;GACxF,MAAM,EAAE,UAAU,iBAAiB;GACnC,IAAI;GACJ,IAAI,UAEF,WAAW,MAAM,KAAK,YACpB;IAAE,YAAY;IAAU,cAAc;IAAI;GAAS,GACnD,EACE,SACF,CACF;QACK,IAAI,cAAc;IAIvB,IAAI,CAAC,cACH,MAAM,IAAI,YAAY,KAAK,kBAAkB,yCAAyC;IAExF,UAAU,KAAK,aAAa,yBAAyB;IACrD,MAAM,QAAQ,WAAW,UAAU,YAAY,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,OAAO,KAAK;IAClF,WAAW,MAAM,cAAc,KAAK,aAAa,cAAc,KAAK;GACtE,OACE,MAAM,IAAI,YAAY,KAAK,mBAAmB,sCAAsC;GAOtF,OAAO;IAAE,OAAA;KAJP,YAAY;KACZ,cAAc,gBAAgB;KAC9B;IAEW;IAAG;GAAS;EAC3B;CACF;AACF;AAEA,MAAa,WAAW,uBAAuB;AAE/C,eAAe,cACb,aACA,aACA,gBACqC;CACrC,MAAM,MAAM,IAAI,IAAI,WAAW;CAC/B,MAAM,SAAS;EAAC;EAAM;EAAQ;EAAS;CAAgC;CACvE,IAAI,aAAa,OAAO,gBAAgB,WAAW;CACnD,IAAI,aAAa,OAAO,UAAU,OAAO,KAAK,GAAG,CAAC;CAClD,IAAI,gBAAgB,IAAI,aAAa,OAAO,mBAAmB,cAAc;CAE7E,MAAM,WAAW,MAAM,MAAM,IAAI,MAAM,EAAE,SAAA,EADvB,QAAQ,mBACqB,EAAE,CAAC;CAClD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,2BAA2B;CAEvF,MAAM,OAAQ,MAAM,SAAS,KAAK;CAClC,OAAO;EACL;EACA,QAAQ;GACN,KAAK,KAAK;GACV,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,SAAS,KAAK,QAAQ,KAAK;EAE7B;CACF;AACF"}
@@ -68,7 +68,7 @@ function createGithubProvider() {
68
68
  return {
69
69
  data,
70
70
  claims: {
71
- sub: data.id,
71
+ sub: String(data.id),
72
72
  name: data.name || data.login,
73
73
  email_verified,
74
74
  email: data.email,
@@ -1 +1 @@
1
- {"version":3,"file":"github.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error"],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAIA,qBAAAA,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAIA,qBAAAA,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"github.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error"],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n // GitHub answers the account id as a number; `sub` is a string claim.\n sub: String(data.id),\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: number;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAIA,qBAAAA,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAIA,qBAAAA,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KAEN,KAAK,OAAO,KAAK,EAAE;KACnB,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -3,7 +3,7 @@ export declare function createGithubProvider(): Provider;
3
3
  export declare const github: Provider;
4
4
  export interface GithubUserInfo {
5
5
  login: string;
6
- id: string;
6
+ id: number;
7
7
  node_id: string;
8
8
  avatar_url: string;
9
9
  gravatar_id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.cts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";wBAmBgB,wBAAwB;qBAqE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"github.d.cts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";wBAmBgB,wBAAwB;qBAsE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
@@ -3,7 +3,7 @@ export declare function createGithubProvider(): Provider;
3
3
  export declare const github: Provider;
4
4
  export interface GithubUserInfo {
5
5
  login: string;
6
- id: string;
6
+ id: number;
7
7
  node_id: string;
8
8
  avatar_url: string;
9
9
  gravatar_id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.mts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";wBAmBgB,wBAAwB;qBAqE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"github.d.mts","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"mappings":";wBAmBgB,wBAAwB;qBAsE3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;;;iBAWa,oBAAoB;EACnC;EACA;EACA;EACA;EACA"}
@@ -67,7 +67,7 @@ function createGithubProvider() {
67
67
  return {
68
68
  data,
69
69
  claims: {
70
- sub: data.id,
70
+ sub: String(data.id),
71
71
  name: data.name || data.login,
72
72
  email_verified,
73
73
  email: data.email,
@@ -1 +1 @@
1
- {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n sub: data.id,\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: string;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAI,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAI,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/oauth2/provider/github.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode } from './common';\n\n/**\n * GitHub's REST API rejects any request without a `User-Agent` header with a 403. Node's fetch sends\n * a default one, but Cloudflare Workers' fetch sends none, so every GitHub request sets it explicitly.\n * reference: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api#user-agent-required\n */\nconst USER_AGENT = '@shware/security';\n\n/** A short slice of an error response body, enough to tell GitHub's failures apart in logs. */\nasync function describeFailure(response: Response, message: string) {\n const body = await response.text().catch(() => '');\n const detail = body ? `: ${body.slice(0, 200)}` : '';\n return `${message} (HTTP ${response.status})${detail}`;\n}\n\nexport function createGithubProvider(): Provider {\n return {\n authorizationUri: 'https://github.com/login/oauth/authorize',\n tokenUri: 'https://github.com/login/oauth/access_token',\n userInfoUri: 'https://api.github.com/user',\n userNameAttribute: 'id',\n defaultScope: ['user:email'],\n createAuthorizationUri({ pkce: _, ...params }) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode({ pkce: _, ...params }) {\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n headers: { 'User-Agent': USER_AGENT },\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GithubErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n // GitHub answers a failed exchange (bad_verification_code, incorrect_client_credentials, ...)\n // with HTTP 200 and an error body, so a 200 alone does not mean a token was issued.\n const body = (await response.json()) as GithubToken & Partial<GithubErrorResponse>;\n if (body.error) throw new OAuth2Error(400, body.error, body.error_description);\n return body;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${access_token}`,\n 'User-Agent': USER_AGENT,\n };\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const message = await describeFailure(response, 'Failed to fetch user info');\n throw new OAuth2Error(response.status, 'invalid_request', message);\n }\n const data = (await response.json()) as GithubUserInfo;\n const emailResponse = await fetch('https://api.github.com/user/emails', { headers });\n if (!emailResponse.ok) {\n const message = await describeFailure(emailResponse, 'Failed to fetch user email');\n throw new OAuth2Error(emailResponse.status, 'invalid_request', message);\n }\n const emails = (await emailResponse.json()) as GithubEmail[];\n data.email = emails.find((email) => email.primary)?.email || data.email;\n const email_verified = emails.find((email) => email.email === data.email)?.verified ?? false;\n return {\n data,\n claims: {\n // GitHub answers the account id as a number; `sub` is a string claim.\n sub: String(data.id),\n name: data.name || data.login,\n email_verified,\n email: data.email,\n picture: data.avatar_url,\n profile: data.html_url,\n website: data.html_url,\n updated_at: data.updated_at ? Date.parse(data.updated_at) : undefined,\n },\n };\n },\n };\n}\n\nexport const github = createGithubProvider();\n\nexport interface GithubUserInfo {\n login: string;\n id: number;\n node_id: string;\n avatar_url: string;\n gravatar_id: string;\n url: string;\n html_url: string;\n followers_url: string;\n following_url: string;\n gists_url: string;\n starred_url: string;\n subscriptions_url: string;\n organizations_url: string;\n repos_url: string;\n events_url: string;\n received_events_url: string;\n type: string;\n site_admin: boolean;\n name: string;\n company: string;\n blog: string;\n location: string;\n email: string;\n hireable: boolean;\n bio: string;\n twitter_username: string;\n public_repos: string;\n public_gists: string;\n followers: string;\n following: string;\n created_at: string;\n updated_at: string;\n private_gists: string;\n total_private_repos: string;\n owned_private_repos: string;\n disk_usage: string;\n collaborators: string;\n two_factor_authentication: boolean;\n plan: {\n name: string;\n space: string;\n private_repos: string;\n collaborators: string;\n };\n}\n\ninterface GithubEmail {\n email: string;\n primary: boolean;\n verified: boolean;\n visibility: 'public' | 'private';\n}\n\nexport interface GithubToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n scope: string;\n expires_in: number;\n refresh_token: string;\n}\n\ninterface GithubErrorResponse {\n error: string;\n error_description: string;\n error_uri: string;\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,aAAa;;AAGnB,eAAe,gBAAgB,UAAoB,SAAiB;CAClE,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACjD,MAAM,SAAS,OAAO,KAAK,KAAK,MAAM,GAAG,GAAG,MAAM;CAClD,OAAO,GAAG,QAAQ,SAAS,SAAS,OAAO,GAAG;AAChD;AAEA,SAAgB,uBAAiC;CAC/C,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,cAAc,CAAC,YAAY;EAC3B,uBAAuB,EAAE,MAAM,GAAG,GAAG,UAAU;GAC7C,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,EAAE,MAAM,GAAG,GAAG,UAAU;GACtD,MAAM,WAAW,MAAM,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,SAAS,EAAE,cAAc,WAAW;GACtC,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GAGA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,IAAI,KAAK,OAAO,MAAM,IAAI,YAAY,KAAK,KAAK,OAAO,KAAK,iBAAiB;GAC7E,OAAO;EACT;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GACrD,MAAM,UAAU;IACd,QAAQ;IACR,eAAe,UAAU;IACzB,cAAc;GAChB;GACA,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,UAAU,MAAM,gBAAgB,UAAU,2BAA2B;IAC3E,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,OAAO;GACnE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,MAAM,gBAAgB,MAAM,MAAM,sCAAsC,EAAE,QAAQ,CAAC;GACnF,IAAI,CAAC,cAAc,IAAI;IACrB,MAAM,UAAU,MAAM,gBAAgB,eAAe,4BAA4B;IACjF,MAAM,IAAI,YAAY,cAAc,QAAQ,mBAAmB,OAAO;GACxE;GACA,MAAM,SAAU,MAAM,cAAc,KAAK;GACzC,KAAK,QAAQ,OAAO,MAAM,UAAU,MAAM,OAAO,CAAC,EAAE,SAAS,KAAK;GAClE,MAAM,iBAAiB,OAAO,MAAM,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC,EAAE,YAAY;GACvF,OAAO;IACL;IACA,QAAQ;KAEN,KAAK,OAAO,KAAK,EAAE;KACnB,MAAM,KAAK,QAAQ,KAAK;KACxB;KACA,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK;KACd,SAAS,KAAK;KACd,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,UAAU,IAAI,KAAA;IAC9D;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -10,7 +10,8 @@ const defaultUserFields = [
10
10
  "url",
11
11
  "username",
12
12
  "location",
13
- "verified"
13
+ "verified",
14
+ "confirmed_email"
14
15
  ];
15
16
  function createXProvider(options) {
16
17
  return {
@@ -22,6 +23,7 @@ function createXProvider(options) {
22
23
  defaultScope: [
23
24
  "users.read",
24
25
  "tweet.read",
26
+ "users.email",
25
27
  "offline.access"
26
28
  ],
27
29
  createAuthorizationUri(params) {
@@ -55,7 +57,7 @@ function createXProvider(options) {
55
57
  };
56
58
  const url = new URL(this.userInfoUri);
57
59
  userFields.forEach((field) => url.searchParams.append("user.fields", field));
58
- const response = await fetch(this.userInfoUri, { headers });
60
+ const response = await fetch(url, { headers });
59
61
  if (!response.ok) {
60
62
  const { errors } = await response.json();
61
63
  throw new require_oauth2_error.OAuth2Error(response.status, "invalid_request", `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`);
@@ -67,8 +69,8 @@ function createXProvider(options) {
67
69
  sub: profile.data.id,
68
70
  name: profile.data.name,
69
71
  picture: profile.data.profile_image_url,
70
- email: profile.data.email,
71
- email_verified: profile.data.verified ?? false,
72
+ email: profile.data.confirmed_email,
73
+ email_verified: profile.data.confirmed_email ? true : void 0,
72
74
  preferred_username: profile.data.username,
73
75
  profile: `https://x.com/${profile.data.username}`,
74
76
  website: profile.data.url
@@ -1 +1 @@
1
- {"version":3,"file":"x.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error","refreshAccessToken","revokeToken"],"sources":["../../../src/oauth2/provider/x.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport {\n createAuthorizationUri,\n exchangeAuthorizationCode,\n refreshAccessToken,\n revokeToken,\n} from './common';\n\nexport interface XOptions {\n userFields?: UserField[];\n}\n\nconst defaultUserFields = [\n 'id',\n 'name',\n 'profile_image_url',\n 'url',\n 'username',\n 'location',\n 'verified',\n];\n\nexport function createXProvider(options?: XOptions): Provider {\n return {\n authorizationUri: 'https://x.com/i/oauth2/authorize',\n tokenUri: 'https://api.x.com/2/oauth2/token',\n tokenRefreshUri: 'https://api.x.com/2/oauth2/token',\n tokenRevokeUri: 'https://api.x.com/2/oauth2/revoke',\n // https://docs.x.com/x-api/users/user-lookup-me\n userInfoUri: 'https://api.x.com/2/users/me',\n defaultScope: ['users.read', 'tweet.read', 'offline.access'],\n createAuthorizationUri(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n\n const userFields = options?.userFields ?? defaultUserFields;\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n\n const url = new URL(this.userInfoUri);\n userFields.forEach((field) => url.searchParams.append('user.fields', field));\n\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const { errors } = (await response.json()) as XAPIErrorResponse;\n throw new OAuth2Error(\n response.status,\n 'invalid_request',\n `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`\n );\n }\n const profile = (await response.json()) as XUserInfo;\n return {\n data: profile,\n claims: {\n sub: profile.data.id,\n name: profile.data.name,\n picture: profile.data.profile_image_url,\n email: profile.data.email,\n email_verified: profile.data.verified ?? false,\n preferred_username: profile.data.username,\n profile: `https://x.com/${profile.data.username}`,\n website: profile.data.url,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const response = await refreshAccessToken({\n ...params,\n tokenUri: this.tokenRefreshUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async revokeToken(params) {\n invariant(this.tokenRevokeUri, 'tokenRevokeUri is required');\n return revokeToken({\n ...params,\n tokenRevokeUri: this.tokenRevokeUri,\n authentication: 'basic',\n });\n },\n };\n}\n\nexport const x = createXProvider();\n\ntype UserField =\n | 'affiliation'\n | 'connection_status'\n | 'created_at'\n | 'description'\n | 'entities'\n | 'id'\n | 'is_identity_verified'\n | 'location'\n | 'most_recent_tweet_id'\n | 'name'\n | 'parody'\n | 'pinned_tweet_id'\n | 'profile_banner_url'\n | 'profile_image_url'\n | 'protected'\n | 'public_metrics'\n | 'receives_your_dm'\n | 'subscription'\n | 'subscription_type'\n | 'url'\n | 'username'\n | 'verified'\n | 'verified_followers_count'\n | 'verified_type'\n | 'withheld';\n\nexport interface XUser {\n id: string;\n name: string;\n email?: string;\n username: string;\n location?: string;\n entities?: {\n url: { urls: UrlEntity[] };\n description: {\n hashtags: HashtagEntity[];\n cashtags: HashtagEntity[];\n mentions: MentionEntity[];\n };\n };\n verified?: boolean;\n description?: string;\n url?: string;\n profile_image_url?: string;\n protected?: boolean;\n pinned_tweet_id?: string;\n created_at?: string;\n}\n\nexport interface XUserInfo {\n data: XUser;\n includes?: { tweets?: { id: string; text: string }[]; users?: XUser[] };\n}\n\ntype UrlEntity = {\n start: number;\n end: number;\n url: string;\n display_url: string;\n expanded_url: string;\n};\ntype HashtagEntity = { start: number; end: number; tag: string };\ntype MentionEntity = { start: number; end: string; username: string };\n\nexport interface XToken extends OAuth2Token {\n token_type: string;\n expires_in: number;\n access_token: string;\n scope: string;\n refresh_token: string;\n}\n\ntype XErrorResponse = { error: string; error_description: string };\ntype XAPIErrorResponse = {\n errors: { title: string; type: string; detail?: string; status?: number }[];\n};\n"],"mappings":";;;;;AAcA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,gBAAgB,SAA8B;CAC5D,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EAEhB,aAAa;EACb,cAAc;GAAC;GAAc;GAAc;EAAgB;EAC3D,uBAAuB,QAAQ;GAC7B,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GAErD,MAAM,aAAa,SAAS,cAAc;GAC1C,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GAEtF,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,WAAW,SAAS,UAAU,IAAI,aAAa,OAAO,eAAe,KAAK,CAAC;GAE3E,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,WAAY,MAAM,SAAS,KAAK;IACxC,MAAM,IAAIA,qBAAAA,YACR,SAAS,QACT,mBACA,8BAA8B,OAAO,GAAG,CAAC,CAAC,EAAE,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,OACtE;GACF;GACA,MAAM,UAAW,MAAM,SAAS,KAAK;GACrC,OAAO;IACL,MAAM;IACN,QAAQ;KACN,KAAK,QAAQ,KAAK;KAClB,MAAM,QAAQ,KAAK;KACnB,SAAS,QAAQ,KAAK;KACtB,OAAO,QAAQ,KAAK;KACpB,gBAAgB,QAAQ,KAAK,YAAY;KACzC,oBAAoB,QAAQ,KAAK;KACjC,SAAS,iBAAiB,QAAQ,KAAK;KACvC,SAAS,QAAQ,KAAK;IACxB;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,WAAW,MAAMC,+BAAAA,mBAAmB;IACxC,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAID,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,QAAQ;GACxB,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,gBAAgB,4BAA4B;GAC3D,OAAOE,+BAAAA,YAAY;IACjB,GAAG;IACH,gBAAgB,KAAK;IACrB,gBAAgB;GAClB,CAAC;EACH;CACF;AACF;AAEA,MAAa,IAAI,gBAAgB"}
1
+ {"version":3,"file":"x.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error","refreshAccessToken","revokeToken"],"sources":["../../../src/oauth2/provider/x.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport {\n createAuthorizationUri,\n exchangeAuthorizationCode,\n refreshAccessToken,\n revokeToken,\n} from './common';\n\nexport interface XOptions {\n userFields?: UserField[];\n}\n\n// `confirmed_email` is the address X has confirmed; it needs the `users.email` scope and\n// \"Request email from users\" enabled on the app in the developer console, else it is absent.\nconst defaultUserFields: UserField[] = [\n 'id',\n 'name',\n 'profile_image_url',\n 'url',\n 'username',\n 'location',\n 'verified',\n 'confirmed_email',\n];\n\nexport function createXProvider(options?: XOptions): Provider {\n return {\n authorizationUri: 'https://x.com/i/oauth2/authorize',\n tokenUri: 'https://api.x.com/2/oauth2/token',\n tokenRefreshUri: 'https://api.x.com/2/oauth2/token',\n tokenRevokeUri: 'https://api.x.com/2/oauth2/revoke',\n // https://docs.x.com/x-api/users/user-lookup-me\n userInfoUri: 'https://api.x.com/2/users/me',\n defaultScope: ['users.read', 'tweet.read', 'users.email', 'offline.access'],\n createAuthorizationUri(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n\n const userFields = options?.userFields ?? defaultUserFields;\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n\n const url = new URL(this.userInfoUri);\n userFields.forEach((field) => url.searchParams.append('user.fields', field));\n\n const response = await fetch(url, { headers });\n if (!response.ok) {\n const { errors } = (await response.json()) as XAPIErrorResponse;\n throw new OAuth2Error(\n response.status,\n 'invalid_request',\n `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`\n );\n }\n const profile = (await response.json()) as XUserInfo;\n return {\n data: profile,\n claims: {\n sub: profile.data.id,\n name: profile.data.name,\n picture: profile.data.profile_image_url,\n // X returns only an address it has confirmed, as `confirmed_email`; `verified` /\n // `verified_type` are the blue check and say nothing about the email.\n email: profile.data.confirmed_email,\n email_verified: profile.data.confirmed_email ? true : undefined,\n preferred_username: profile.data.username,\n profile: `https://x.com/${profile.data.username}`,\n website: profile.data.url,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const response = await refreshAccessToken({\n ...params,\n tokenUri: this.tokenRefreshUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async revokeToken(params) {\n invariant(this.tokenRevokeUri, 'tokenRevokeUri is required');\n return revokeToken({\n ...params,\n tokenRevokeUri: this.tokenRevokeUri,\n authentication: 'basic',\n });\n },\n };\n}\n\nexport const x = createXProvider();\n\ntype UserField =\n | 'affiliation'\n | 'confirmed_email'\n | 'connection_status'\n | 'created_at'\n | 'description'\n | 'entities'\n | 'id'\n | 'is_identity_verified'\n | 'location'\n | 'most_recent_tweet_id'\n | 'name'\n | 'parody'\n | 'pinned_tweet_id'\n | 'profile_banner_url'\n | 'profile_image_url'\n | 'protected'\n | 'public_metrics'\n | 'receives_your_dm'\n | 'subscription'\n | 'subscription_type'\n | 'url'\n | 'username'\n | 'verified'\n | 'verified_followers_count'\n | 'verified_type'\n | 'withheld';\n\nexport interface XUser {\n id: string;\n name: string;\n /** The address X has confirmed; only with the `users.email` scope and `confirmed_email` requested. */\n confirmed_email?: string;\n username: string;\n location?: string;\n entities?: {\n url: { urls: UrlEntity[] };\n description: {\n hashtags: HashtagEntity[];\n cashtags: HashtagEntity[];\n mentions: MentionEntity[];\n };\n };\n verified?: boolean;\n description?: string;\n url?: string;\n profile_image_url?: string;\n protected?: boolean;\n pinned_tweet_id?: string;\n created_at?: string;\n}\n\nexport interface XUserInfo {\n data: XUser;\n includes?: { tweets?: { id: string; text: string }[]; users?: XUser[] };\n}\n\ntype UrlEntity = {\n start: number;\n end: number;\n url: string;\n display_url: string;\n expanded_url: string;\n};\ntype HashtagEntity = { start: number; end: number; tag: string };\ntype MentionEntity = { start: number; end: string; username: string };\n\nexport interface XToken extends OAuth2Token {\n token_type: string;\n expires_in: number;\n access_token: string;\n scope: string;\n refresh_token: string;\n}\n\ntype XErrorResponse = { error: string; error_description: string };\ntype XAPIErrorResponse = {\n errors: { title: string; type: string; detail?: string; status?: number }[];\n};\n"],"mappings":";;;;;AAgBA,MAAM,oBAAiC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,gBAAgB,SAA8B;CAC5D,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EAEhB,aAAa;EACb,cAAc;GAAC;GAAc;GAAc;GAAe;EAAgB;EAC1E,uBAAuB,QAAQ;GAC7B,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAIC,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,CAAA,GAAA,cAAA,UAAA,CAAU,cAAc,0BAA0B;GAClD,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GAErD,MAAM,aAAa,SAAS,cAAc;GAC1C,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GAEtF,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,WAAW,SAAS,UAAU,IAAI,aAAa,OAAO,eAAe,KAAK,CAAC;GAE3E,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;GAC7C,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,WAAY,MAAM,SAAS,KAAK;IACxC,MAAM,IAAIA,qBAAAA,YACR,SAAS,QACT,mBACA,8BAA8B,OAAO,GAAG,CAAC,CAAC,EAAE,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,OACtE;GACF;GACA,MAAM,UAAW,MAAM,SAAS,KAAK;GACrC,OAAO;IACL,MAAM;IACN,QAAQ;KACN,KAAK,QAAQ,KAAK;KAClB,MAAM,QAAQ,KAAK;KACnB,SAAS,QAAQ,KAAK;KAGtB,OAAO,QAAQ,KAAK;KACpB,gBAAgB,QAAQ,KAAK,kBAAkB,OAAO,KAAA;KACtD,oBAAoB,QAAQ,KAAK;KACjC,SAAS,iBAAiB,QAAQ,KAAK;KACvC,SAAS,QAAQ,KAAK;IACxB;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,WAAW,MAAMC,+BAAAA,mBAAmB;IACxC,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAID,qBAAAA,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,QAAQ;GACxB,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,gBAAgB,4BAA4B;GAC3D,OAAOE,+BAAAA,YAAY;IACjB,GAAG;IACH,gBAAgB,KAAK;IACrB,gBAAgB;GAClB,CAAC;EACH;CACF;AACF;AAEA,MAAa,IAAI,gBAAgB"}
@@ -4,11 +4,12 @@ export interface XOptions {
4
4
  }
5
5
  export declare function createXProvider(options?: XOptions): Provider;
6
6
  export declare const x: Provider;
7
- type UserField = 'affiliation' | 'connection_status' | 'created_at' | 'description' | 'entities' | 'id' | 'is_identity_verified' | 'location' | 'most_recent_tweet_id' | 'name' | 'parody' | 'pinned_tweet_id' | 'profile_banner_url' | 'profile_image_url' | 'protected' | 'public_metrics' | 'receives_your_dm' | 'subscription' | 'subscription_type' | 'url' | 'username' | 'verified' | 'verified_followers_count' | 'verified_type' | 'withheld';
7
+ type UserField = 'affiliation' | 'confirmed_email' | 'connection_status' | 'created_at' | 'description' | 'entities' | 'id' | 'is_identity_verified' | 'location' | 'most_recent_tweet_id' | 'name' | 'parody' | 'pinned_tweet_id' | 'profile_banner_url' | 'profile_image_url' | 'protected' | 'public_metrics' | 'receives_your_dm' | 'subscription' | 'subscription_type' | 'url' | 'username' | 'verified' | 'verified_followers_count' | 'verified_type' | 'withheld';
8
8
  export interface XUser {
9
9
  id: string;
10
10
  name: string;
11
- email?: string;
11
+ /** The address X has confirmed; only with the `users.email` scope and `confirmed_email` requested. */
12
+ confirmed_email?: string;
12
13
  username: string;
13
14
  location?: string;
14
15
  entities?: {
@@ -1 +1 @@
1
- {"version":3,"file":"x.d.cts","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"mappings":";iBAUiB;EACf,aAAa;;wBAaC,gBAAgB,UAAU,WAAW;qBAwFxC,GAAC;KAET;iBA2BY;EACf;EACA;EACA;EACA;EACA;EACA;IACE;MAAO,MAAM;;IACb;MACE,UAAU;MACV,UAAU;MACV,UAAU;;;EAGd;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,MAAM;EACN;IAAa;MAAW;MAAY;;IAAkB,QAAQ;;;KAG3D;EACH;EACA;EACA;EACA;EACA;;KAEG;EAAkB;EAAe;EAAa;;KAC9C;EAAkB;EAAe;EAAa;;iBAElC,eAAe;EAC9B;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"x.d.cts","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"mappings":";iBAUiB;EACf,aAAa;;wBAgBC,gBAAgB,UAAU,WAAW;qBA0FxC,GAAC;KAET;iBA4BY;EACf;EACA;;EAEA;EACA;EACA;EACA;IACE;MAAO,MAAM;;IACb;MACE,UAAU;MACV,UAAU;MACV,UAAU;;;EAGd;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,MAAM;EACN;IAAa;MAAW;MAAY;;IAAkB,QAAQ;;;KAG3D;EACH;EACA;EACA;EACA;EACA;;KAEG;EAAkB;EAAe;EAAa;;KAC9C;EAAkB;EAAe;EAAa;;iBAElC,eAAe;EAC9B;EACA;EACA;EACA;EACA"}
@@ -4,11 +4,12 @@ export interface XOptions {
4
4
  }
5
5
  export declare function createXProvider(options?: XOptions): Provider;
6
6
  export declare const x: Provider;
7
- type UserField = 'affiliation' | 'connection_status' | 'created_at' | 'description' | 'entities' | 'id' | 'is_identity_verified' | 'location' | 'most_recent_tweet_id' | 'name' | 'parody' | 'pinned_tweet_id' | 'profile_banner_url' | 'profile_image_url' | 'protected' | 'public_metrics' | 'receives_your_dm' | 'subscription' | 'subscription_type' | 'url' | 'username' | 'verified' | 'verified_followers_count' | 'verified_type' | 'withheld';
7
+ type UserField = 'affiliation' | 'confirmed_email' | 'connection_status' | 'created_at' | 'description' | 'entities' | 'id' | 'is_identity_verified' | 'location' | 'most_recent_tweet_id' | 'name' | 'parody' | 'pinned_tweet_id' | 'profile_banner_url' | 'profile_image_url' | 'protected' | 'public_metrics' | 'receives_your_dm' | 'subscription' | 'subscription_type' | 'url' | 'username' | 'verified' | 'verified_followers_count' | 'verified_type' | 'withheld';
8
8
  export interface XUser {
9
9
  id: string;
10
10
  name: string;
11
- email?: string;
11
+ /** The address X has confirmed; only with the `users.email` scope and `confirmed_email` requested. */
12
+ confirmed_email?: string;
12
13
  username: string;
13
14
  location?: string;
14
15
  entities?: {
@@ -1 +1 @@
1
- {"version":3,"file":"x.d.mts","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"mappings":";iBAUiB;EACf,aAAa;;wBAaC,gBAAgB,UAAU,WAAW;qBAwFxC,GAAC;KAET;iBA2BY;EACf;EACA;EACA;EACA;EACA;EACA;IACE;MAAO,MAAM;;IACb;MACE,UAAU;MACV,UAAU;MACV,UAAU;;;EAGd;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,MAAM;EACN;IAAa;MAAW;MAAY;;IAAkB,QAAQ;;;KAG3D;EACH;EACA;EACA;EACA;EACA;;KAEG;EAAkB;EAAe;EAAa;;KAC9C;EAAkB;EAAe;EAAa;;iBAElC,eAAe;EAC9B;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"x.d.mts","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"mappings":";iBAUiB;EACf,aAAa;;wBAgBC,gBAAgB,UAAU,WAAW;qBA0FxC,GAAC;KAET;iBA4BY;EACf;EACA;;EAEA;EACA;EACA;EACA;IACE;MAAO,MAAM;;IACb;MACE,UAAU;MACV,UAAU;MACV,UAAU;;;EAGd;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,MAAM;EACN;IAAa;MAAW;MAAY;;IAAkB,QAAQ;;;KAG3D;EACH;EACA;EACA;EACA;EACA;;KAEG;EAAkB;EAAe;EAAa;;KAC9C;EAAkB;EAAe;EAAa;;iBAElC,eAAe;EAC9B;EACA;EACA;EACA;EACA"}
@@ -9,7 +9,8 @@ const defaultUserFields = [
9
9
  "url",
10
10
  "username",
11
11
  "location",
12
- "verified"
12
+ "verified",
13
+ "confirmed_email"
13
14
  ];
14
15
  function createXProvider(options) {
15
16
  return {
@@ -21,6 +22,7 @@ function createXProvider(options) {
21
22
  defaultScope: [
22
23
  "users.read",
23
24
  "tweet.read",
25
+ "users.email",
24
26
  "offline.access"
25
27
  ],
26
28
  createAuthorizationUri(params) {
@@ -54,7 +56,7 @@ function createXProvider(options) {
54
56
  };
55
57
  const url = new URL(this.userInfoUri);
56
58
  userFields.forEach((field) => url.searchParams.append("user.fields", field));
57
- const response = await fetch(this.userInfoUri, { headers });
59
+ const response = await fetch(url, { headers });
58
60
  if (!response.ok) {
59
61
  const { errors } = await response.json();
60
62
  throw new OAuth2Error(response.status, "invalid_request", `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`);
@@ -66,8 +68,8 @@ function createXProvider(options) {
66
68
  sub: profile.data.id,
67
69
  name: profile.data.name,
68
70
  picture: profile.data.profile_image_url,
69
- email: profile.data.email,
70
- email_verified: profile.data.verified ?? false,
71
+ email: profile.data.confirmed_email,
72
+ email_verified: profile.data.confirmed_email ? true : void 0,
71
73
  preferred_username: profile.data.username,
72
74
  profile: `https://x.com/${profile.data.username}`,
73
75
  website: profile.data.url
@@ -1 +1 @@
1
- {"version":3,"file":"x.mjs","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport {\n createAuthorizationUri,\n exchangeAuthorizationCode,\n refreshAccessToken,\n revokeToken,\n} from './common';\n\nexport interface XOptions {\n userFields?: UserField[];\n}\n\nconst defaultUserFields = [\n 'id',\n 'name',\n 'profile_image_url',\n 'url',\n 'username',\n 'location',\n 'verified',\n];\n\nexport function createXProvider(options?: XOptions): Provider {\n return {\n authorizationUri: 'https://x.com/i/oauth2/authorize',\n tokenUri: 'https://api.x.com/2/oauth2/token',\n tokenRefreshUri: 'https://api.x.com/2/oauth2/token',\n tokenRevokeUri: 'https://api.x.com/2/oauth2/revoke',\n // https://docs.x.com/x-api/users/user-lookup-me\n userInfoUri: 'https://api.x.com/2/users/me',\n defaultScope: ['users.read', 'tweet.read', 'offline.access'],\n createAuthorizationUri(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n\n const userFields = options?.userFields ?? defaultUserFields;\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n\n const url = new URL(this.userInfoUri);\n userFields.forEach((field) => url.searchParams.append('user.fields', field));\n\n const response = await fetch(this.userInfoUri, { headers });\n if (!response.ok) {\n const { errors } = (await response.json()) as XAPIErrorResponse;\n throw new OAuth2Error(\n response.status,\n 'invalid_request',\n `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`\n );\n }\n const profile = (await response.json()) as XUserInfo;\n return {\n data: profile,\n claims: {\n sub: profile.data.id,\n name: profile.data.name,\n picture: profile.data.profile_image_url,\n email: profile.data.email,\n email_verified: profile.data.verified ?? false,\n preferred_username: profile.data.username,\n profile: `https://x.com/${profile.data.username}`,\n website: profile.data.url,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const response = await refreshAccessToken({\n ...params,\n tokenUri: this.tokenRefreshUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async revokeToken(params) {\n invariant(this.tokenRevokeUri, 'tokenRevokeUri is required');\n return revokeToken({\n ...params,\n tokenRevokeUri: this.tokenRevokeUri,\n authentication: 'basic',\n });\n },\n };\n}\n\nexport const x = createXProvider();\n\ntype UserField =\n | 'affiliation'\n | 'connection_status'\n | 'created_at'\n | 'description'\n | 'entities'\n | 'id'\n | 'is_identity_verified'\n | 'location'\n | 'most_recent_tweet_id'\n | 'name'\n | 'parody'\n | 'pinned_tweet_id'\n | 'profile_banner_url'\n | 'profile_image_url'\n | 'protected'\n | 'public_metrics'\n | 'receives_your_dm'\n | 'subscription'\n | 'subscription_type'\n | 'url'\n | 'username'\n | 'verified'\n | 'verified_followers_count'\n | 'verified_type'\n | 'withheld';\n\nexport interface XUser {\n id: string;\n name: string;\n email?: string;\n username: string;\n location?: string;\n entities?: {\n url: { urls: UrlEntity[] };\n description: {\n hashtags: HashtagEntity[];\n cashtags: HashtagEntity[];\n mentions: MentionEntity[];\n };\n };\n verified?: boolean;\n description?: string;\n url?: string;\n profile_image_url?: string;\n protected?: boolean;\n pinned_tweet_id?: string;\n created_at?: string;\n}\n\nexport interface XUserInfo {\n data: XUser;\n includes?: { tweets?: { id: string; text: string }[]; users?: XUser[] };\n}\n\ntype UrlEntity = {\n start: number;\n end: number;\n url: string;\n display_url: string;\n expanded_url: string;\n};\ntype HashtagEntity = { start: number; end: number; tag: string };\ntype MentionEntity = { start: number; end: string; username: string };\n\nexport interface XToken extends OAuth2Token {\n token_type: string;\n expires_in: number;\n access_token: string;\n scope: string;\n refresh_token: string;\n}\n\ntype XErrorResponse = { error: string; error_description: string };\ntype XAPIErrorResponse = {\n errors: { title: string; type: string; detail?: string; status?: number }[];\n};\n"],"mappings":";;;;AAcA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,gBAAgB,SAA8B;CAC5D,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EAEhB,aAAa;EACb,cAAc;GAAC;GAAc;GAAc;EAAgB;EAC3D,uBAAuB,QAAQ;GAC7B,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,MAAM,WAAW,MAAM,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GAErD,MAAM,aAAa,SAAS,cAAc;GAC1C,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GAEtF,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,WAAW,SAAS,UAAU,IAAI,aAAa,OAAO,eAAe,KAAK,CAAC;GAE3E,MAAM,WAAW,MAAM,MAAM,KAAK,aAAa,EAAE,QAAQ,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,WAAY,MAAM,SAAS,KAAK;IACxC,MAAM,IAAI,YACR,SAAS,QACT,mBACA,8BAA8B,OAAO,GAAG,CAAC,CAAC,EAAE,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,OACtE;GACF;GACA,MAAM,UAAW,MAAM,SAAS,KAAK;GACrC,OAAO;IACL,MAAM;IACN,QAAQ;KACN,KAAK,QAAQ,KAAK;KAClB,MAAM,QAAQ,KAAK;KACnB,SAAS,QAAQ,KAAK;KACtB,OAAO,QAAQ,KAAK;KACpB,gBAAgB,QAAQ,KAAK,YAAY;KACzC,oBAAoB,QAAQ,KAAK;KACjC,SAAS,iBAAiB,QAAQ,KAAK;KACvC,SAAS,QAAQ,KAAK;IACxB;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,UAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,WAAW,MAAM,mBAAmB;IACxC,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,QAAQ;GACxB,UAAU,KAAK,gBAAgB,4BAA4B;GAC3D,OAAO,YAAY;IACjB,GAAG;IACH,gBAAgB,KAAK;IACrB,gBAAgB;GAClB,CAAC;EACH;CACF;AACF;AAEA,MAAa,IAAI,gBAAgB"}
1
+ {"version":3,"file":"x.mjs","names":[],"sources":["../../../src/oauth2/provider/x.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport {\n createAuthorizationUri,\n exchangeAuthorizationCode,\n refreshAccessToken,\n revokeToken,\n} from './common';\n\nexport interface XOptions {\n userFields?: UserField[];\n}\n\n// `confirmed_email` is the address X has confirmed; it needs the `users.email` scope and\n// \"Request email from users\" enabled on the app in the developer console, else it is absent.\nconst defaultUserFields: UserField[] = [\n 'id',\n 'name',\n 'profile_image_url',\n 'url',\n 'username',\n 'location',\n 'verified',\n 'confirmed_email',\n];\n\nexport function createXProvider(options?: XOptions): Provider {\n return {\n authorizationUri: 'https://x.com/i/oauth2/authorize',\n tokenUri: 'https://api.x.com/2/oauth2/token',\n tokenRefreshUri: 'https://api.x.com/2/oauth2/token',\n tokenRevokeUri: 'https://api.x.com/2/oauth2/revoke',\n // https://docs.x.com/x-api/users/user-lookup-me\n userInfoUri: 'https://api.x.com/2/users/me',\n defaultScope: ['users.read', 'tweet.read', 'users.email', 'offline.access'],\n createAuthorizationUri(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n if (!params.pkce) throw new Error('pkce is required for x');\n const response = await exchangeAuthorizationCode({\n ...params,\n tokenUri: this.tokenUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async getUserInfo({ access_token }) {\n invariant(access_token, 'access_token is required');\n invariant(this.userInfoUri, 'userInfoUri is required');\n\n const userFields = options?.userFields ?? defaultUserFields;\n const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n\n const url = new URL(this.userInfoUri);\n userFields.forEach((field) => url.searchParams.append('user.fields', field));\n\n const response = await fetch(url, { headers });\n if (!response.ok) {\n const { errors } = (await response.json()) as XAPIErrorResponse;\n throw new OAuth2Error(\n response.status,\n 'invalid_request',\n `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`\n );\n }\n const profile = (await response.json()) as XUserInfo;\n return {\n data: profile,\n claims: {\n sub: profile.data.id,\n name: profile.data.name,\n picture: profile.data.profile_image_url,\n // X returns only an address it has confirmed, as `confirmed_email`; `verified` /\n // `verified_type` are the blue check and say nothing about the email.\n email: profile.data.confirmed_email,\n email_verified: profile.data.confirmed_email ? true : undefined,\n preferred_username: profile.data.username,\n profile: `https://x.com/${profile.data.username}`,\n website: profile.data.url,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const response = await refreshAccessToken({\n ...params,\n tokenUri: this.tokenRefreshUri,\n authentication: 'basic',\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as XErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as XToken;\n },\n async revokeToken(params) {\n invariant(this.tokenRevokeUri, 'tokenRevokeUri is required');\n return revokeToken({\n ...params,\n tokenRevokeUri: this.tokenRevokeUri,\n authentication: 'basic',\n });\n },\n };\n}\n\nexport const x = createXProvider();\n\ntype UserField =\n | 'affiliation'\n | 'confirmed_email'\n | 'connection_status'\n | 'created_at'\n | 'description'\n | 'entities'\n | 'id'\n | 'is_identity_verified'\n | 'location'\n | 'most_recent_tweet_id'\n | 'name'\n | 'parody'\n | 'pinned_tweet_id'\n | 'profile_banner_url'\n | 'profile_image_url'\n | 'protected'\n | 'public_metrics'\n | 'receives_your_dm'\n | 'subscription'\n | 'subscription_type'\n | 'url'\n | 'username'\n | 'verified'\n | 'verified_followers_count'\n | 'verified_type'\n | 'withheld';\n\nexport interface XUser {\n id: string;\n name: string;\n /** The address X has confirmed; only with the `users.email` scope and `confirmed_email` requested. */\n confirmed_email?: string;\n username: string;\n location?: string;\n entities?: {\n url: { urls: UrlEntity[] };\n description: {\n hashtags: HashtagEntity[];\n cashtags: HashtagEntity[];\n mentions: MentionEntity[];\n };\n };\n verified?: boolean;\n description?: string;\n url?: string;\n profile_image_url?: string;\n protected?: boolean;\n pinned_tweet_id?: string;\n created_at?: string;\n}\n\nexport interface XUserInfo {\n data: XUser;\n includes?: { tweets?: { id: string; text: string }[]; users?: XUser[] };\n}\n\ntype UrlEntity = {\n start: number;\n end: number;\n url: string;\n display_url: string;\n expanded_url: string;\n};\ntype HashtagEntity = { start: number; end: number; tag: string };\ntype MentionEntity = { start: number; end: string; username: string };\n\nexport interface XToken extends OAuth2Token {\n token_type: string;\n expires_in: number;\n access_token: string;\n scope: string;\n refresh_token: string;\n}\n\ntype XErrorResponse = { error: string; error_description: string };\ntype XAPIErrorResponse = {\n errors: { title: string; type: string; detail?: string; status?: number }[];\n};\n"],"mappings":";;;;AAgBA,MAAM,oBAAiC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,gBAAgB,SAA8B;CAC5D,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EAEhB,aAAa;EACb,cAAc;GAAC;GAAc;GAAc;GAAe;EAAgB;EAC1E,uBAAuB,QAAQ;GAC7B,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,IAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,wBAAwB;GAC1D,MAAM,WAAW,MAAM,0BAA0B;IAC/C,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,gBAAgB;GAClC,UAAU,cAAc,0BAA0B;GAClD,UAAU,KAAK,aAAa,yBAAyB;GAErD,MAAM,aAAa,SAAS,cAAc;GAC1C,MAAM,UAAU;IAAE,QAAQ;IAAoB,eAAe,UAAU;GAAe;GAEtF,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,WAAW,SAAS,UAAU,IAAI,aAAa,OAAO,eAAe,KAAK,CAAC;GAE3E,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;GAC7C,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,WAAY,MAAM,SAAS,KAAK;IACxC,MAAM,IAAI,YACR,SAAS,QACT,mBACA,8BAA8B,OAAO,GAAG,CAAC,CAAC,EAAE,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,OACtE;GACF;GACA,MAAM,UAAW,MAAM,SAAS,KAAK;GACrC,OAAO;IACL,MAAM;IACN,QAAQ;KACN,KAAK,QAAQ,KAAK;KAClB,MAAM,QAAQ,KAAK;KACnB,SAAS,QAAQ,KAAK;KAGtB,OAAO,QAAQ,KAAK;KACpB,gBAAgB,QAAQ,KAAK,kBAAkB,OAAO,KAAA;KACtD,oBAAoB,QAAQ,KAAK;KACjC,SAAS,iBAAiB,QAAQ,KAAK;KACvC,SAAS,QAAQ,KAAK;IACxB;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,UAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,WAAW,MAAM,mBAAmB;IACxC,GAAG;IACH,UAAU,KAAK;IACf,gBAAgB;GAClB,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,QAAQ;GACxB,UAAU,KAAK,gBAAgB,4BAA4B;GAC3D,OAAO,YAAY;IACjB,GAAG;IACH,gBAAgB,KAAK;IACrB,gBAAgB;GAClB,CAAC;EACH;CACF;AACF;AAEA,MAAa,IAAI,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","names":[],"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n /** Key of the provider in `OAuth2ClientConfig.providers` the flow was started for. */\n provider: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n /**\n * What the host attached when the flow started (`oauth2Authorization` options), kept on the\n * server and handed back to `onAuthorized` once the callback checks out. Never sent to the\n * provider, unlike `additionalParameters`. Absent on requests stored before this field existed.\n */\n attributes?: Record<string, string>;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n /** Absent for public clients (Google's installed-app clients, PKCE-only clients). */\n clientSecret?: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret?: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n /**\n * The configured client the app signs in as (its `client_id`, or the provider's default native\n * client). Id tokens must be issued to it: it is the only audience accepted.\n */\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n /**\n * The PKCE verifier for the code exchange, taken from `credentials.code_verifier`: the app made\n * the challenge and keeps the verifier, so the server only ever has the verifier. Providers\n * whose token endpoint takes `code_verifier` send it; the others ignore it.\n */\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n /** What the app posted, without `code_verifier`, which arrives as `pkce`. */\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n /**\n * The PKCE verifier (RFC 7636) for `code`, when the app sent a `code_challenge` with the\n * authorization request: 43-128 characters from `[A-Za-z0-9-._~]`. Sent on to the provider's\n * token endpoint, which refuses the code unless the verifier matches its challenge, so a code\n * intercepted on the redirect (a custom-scheme redirect any app can claim) is useless alone.\n * RFC 8252 §8.1 requires it for native apps, whose installed-app clients have no secret.\n */\n code_verifier?: string;\n id_token?: string;\n access_token?: string;\n /**\n * The user's name as the platform hands it to the app, for providers whose id token carries none\n * (Apple sends it on the first authorization only): iOS\n * `ASAuthorizationAppleIDCredential.fullName`, expo-apple-authentication's `credential.fullName`.\n * The snake_case fields above are OAuth / OIDC protocol parameters; this one is not, it is the\n * platform's object passed through unchanged, so it keeps the platform's own name and apps send\n * `fullName: credential.fullName` as is. Unsigned client input: the provider cleans each part and\n * ignores anything that is not a string. Parts with a standard claim become `given_name`,\n * `family_name`, `middle_name` and `nickname`; `namePrefix` / `nameSuffix` have none and are kept\n * in `userInfo.data.fullName` only.\n */\n fullName?: PersonNameComponents;\n};\n\n/** iOS `PersonNameComponents` as JSON; every part may be missing or `null`. */\nexport interface PersonNameComponents {\n givenName?: string | null;\n familyName?: string | null;\n middleName?: string | null;\n namePrefix?: string | null;\n nameSuffix?: string | null;\n nickname?: string | null;\n}\n\n/**\n * Fields of the authorization response besides `code` and `state` that a provider uses to fill in\n * the user info: Apple posts the user's name as `user` (JSON) with the first authorization only,\n * since its id token never carries one. Unsigned, so treated as untrusted input.\n */\nexport interface AuthorizationResponse {\n user?: string;\n}\n\nexport const OIDC_SCOPES = [\n 'openid',\n 'profile',\n 'email',\n 'phone',\n 'address',\n 'offline_access',\n] as const;\n\nexport type OidcScope = (typeof OIDC_SCOPES)[number];\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n email_verified?: boolean;\n gender?: 'female' | 'male' | (string & {});\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /**\n * step 3: get user info. `context.clientId` is the client the token was issued to, the only\n * audience an id token may carry; `context.response` holds what the authorization response\n * carried besides the code, for the providers that send user data there (Apple).\n */\n getUserInfo: (token: OAuth2Token, context: UserInfoContext) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface UserInfoContext {\n clientId: string;\n response?: AuthorizationResponse;\n}\n\nexport interface OneTapProvider extends Provider {\n /** Verifies a One Tap / FedCM credential, which must be issued to `clientId` (the web client). */\n getTokenInfo: (\n id_token: string,\n clientId: string\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\n/**\n * A client secret: a fixed string, or a function minting one for the client id it is called with\n * (Sign in with Apple's signed JWT, see `createAppleClientSecret`), called on every use so it can\n * cache and renew on its own.\n */\nexport type ClientSecret = string | ((clientId: string) => string | Promise<string>);\n\n/** The client a provider's server-side flows sign in as: the redirect flow and Google One Tap. */\nexport interface WebClient {\n clientId: string;\n /** Absent for a public client. */\n clientSecret?: ClientSecret;\n /** Default: `${baseUri}/login/oauth2/code/<provider key>`. */\n redirectUri?: string;\n /** Authorization scopes. Default: the provider's `defaultScope`. */\n scope?: string[];\n}\n\n/**\n * A client an app signs in as on the device (an iOS bundle id, a Google iOS / Android client) and\n * whose code or id token it posts to the native login route.\n */\nexport interface NativeClient {\n clientId: string;\n /** Absent for a public client (Google's installed-app clients have none). */\n clientSecret?: ClientSecret;\n /**\n * Redirect URI sent with the code exchange when the app sends no `redirect_uri` of its own.\n * Default: `${baseUri}/login/oauth2/code/<provider key>`.\n */\n redirectUri?: string;\n}\n\n/**\n * One identity source: every user it signs in has a `sub` unique within it, whichever client they\n * came through. The key in `OAuth2ClientConfig.providers` names it in the routes and in\n * `onAuthorized`. Two keys may share an implementation (two Google Cloud projects, one Okta per\n * tenant) when their subjects are separate namespaces.\n */\nexport interface ProviderConfig {\n /** The implementation: `apple`, `google`, `github`, ... from `@shware/security/oauth2/provider`. */\n provider: Provider;\n /** The redirect flow's and One Tap's client. Required by those flows. */\n web?: WebClient;\n /**\n * Clients the native login accepts besides `web`. An app names its client with `client_id`;\n * without one, the only native client is used, or `web` when there is none.\n */\n native?: NativeClient[];\n}\n\nexport type ProvidersConfig = { [key: string]: ProviderConfig | undefined };\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n /** Providers by key; a function is called per request, for secrets read at request time. */\n providers: ProvidersConfig | (() => Promise<ProvidersConfig>);\n}\n"],"mappings":";;AAkKA,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"types.cjs","names":[],"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n /** Key of the provider in `OAuth2ClientConfig.providers` the flow was started for. */\n provider: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n /**\n * What the host attached when the flow started (`oauth2Authorization` options), kept on the\n * server and handed back to `onAuthorized` once the callback checks out. Never sent to the\n * provider, unlike `additionalParameters`. Absent on requests stored before this field existed.\n */\n attributes?: Record<string, string>;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n /** Absent for public clients (Google's installed-app clients, PKCE-only clients). */\n clientSecret?: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret?: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n /**\n * The configured client the app signs in as (its `client_id`, or the provider's default native\n * client). Id tokens must be issued to it: it is the only audience accepted.\n */\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n /**\n * The PKCE verifier for the code exchange, taken from `credentials.code_verifier`: the app made\n * the challenge and keeps the verifier, so the server only ever has the verifier. Providers\n * whose token endpoint takes `code_verifier` send it; the others ignore it.\n */\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n /** What the app posted, without `code_verifier`, which arrives as `pkce`. */\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n /**\n * The PKCE verifier (RFC 7636) for `code`, when the app sent a `code_challenge` with the\n * authorization request: 43-128 characters from `[A-Za-z0-9-._~]`. Sent on to the provider's\n * token endpoint, which refuses the code unless the verifier matches its challenge, so a code\n * intercepted on the redirect (a custom-scheme redirect any app can claim) is useless alone.\n * RFC 8252 §8.1 requires it for native apps, whose installed-app clients have no secret.\n */\n code_verifier?: string;\n id_token?: string;\n access_token?: string;\n /**\n * The user's name as the platform hands it to the app, for providers whose id token carries none\n * (Apple sends it on the first authorization only): iOS\n * `ASAuthorizationAppleIDCredential.fullName`, expo-apple-authentication's `credential.fullName`.\n * The snake_case fields above are OAuth / OIDC protocol parameters; this one is not, it is the\n * platform's object passed through unchanged, so it keeps the platform's own name and apps send\n * `fullName: credential.fullName` as is. Unsigned client input: the provider cleans each part and\n * ignores anything that is not a string. Parts with a standard claim become `given_name`,\n * `family_name`, `middle_name` and `nickname`; `namePrefix` / `nameSuffix` have none and are kept\n * in `userInfo.data.fullName` only.\n */\n fullName?: PersonNameComponents;\n};\n\n/** iOS `PersonNameComponents` as JSON; every part may be missing or `null`. */\nexport interface PersonNameComponents {\n givenName?: string | null;\n familyName?: string | null;\n middleName?: string | null;\n namePrefix?: string | null;\n nameSuffix?: string | null;\n nickname?: string | null;\n}\n\n/**\n * Fields of the authorization response besides `code` and `state` that a provider uses to fill in\n * the user info: Apple posts the user's name as `user` (JSON) with the first authorization only,\n * since its id token never carries one. Unsigned, so treated as untrusted input.\n */\nexport interface AuthorizationResponse {\n user?: string;\n}\n\nexport const OIDC_SCOPES = [\n 'openid',\n 'profile',\n 'email',\n 'phone',\n 'address',\n 'offline_access',\n] as const;\n\nexport type OidcScope = (typeof OIDC_SCOPES)[number];\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n /**\n * Set only by providers that state it (Google, Apple, GitHub from the primary address's\n * `verified` flag, X when it returns `confirmed_email`); Facebook leaves it unset. Trust the\n * address only when this is `true`.\n */\n email_verified?: boolean;\n gender?: 'female' | 'male' | (string & {});\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /**\n * step 3: get user info. `context.clientId` is the client the token was issued to, the only\n * audience an id token may carry; `context.response` holds what the authorization response\n * carried besides the code, for the providers that send user data there (Apple).\n */\n getUserInfo: (token: OAuth2Token, context: UserInfoContext) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface UserInfoContext {\n clientId: string;\n response?: AuthorizationResponse;\n}\n\nexport interface OneTapProvider extends Provider {\n /** Verifies a One Tap / FedCM credential, which must be issued to `clientId` (the web client). */\n getTokenInfo: (\n id_token: string,\n clientId: string\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\n/**\n * A client secret: a fixed string, or a function minting one for the client id it is called with\n * (Sign in with Apple's signed JWT, see `createAppleClientSecret`), called on every use so it can\n * cache and renew on its own.\n */\nexport type ClientSecret = string | ((clientId: string) => string | Promise<string>);\n\n/** The client a provider's server-side flows sign in as: the redirect flow and Google One Tap. */\nexport interface WebClient {\n clientId: string;\n /** Absent for a public client. */\n clientSecret?: ClientSecret;\n /** Default: `${baseUri}/login/oauth2/code/<provider key>`. */\n redirectUri?: string;\n /** Authorization scopes. Default: the provider's `defaultScope`. */\n scope?: string[];\n}\n\n/**\n * A client an app signs in as on the device (an iOS bundle id, a Google iOS / Android client) and\n * whose code or id token it posts to the native login route.\n */\nexport interface NativeClient {\n clientId: string;\n /** Absent for a public client (Google's installed-app clients have none). */\n clientSecret?: ClientSecret;\n /**\n * Redirect URI sent with the code exchange when the app sends no `redirect_uri` of its own.\n * Default: `${baseUri}/login/oauth2/code/<provider key>`.\n */\n redirectUri?: string;\n}\n\n/**\n * One identity source: every user it signs in has a `sub` unique within it, whichever client they\n * came through. The key in `OAuth2ClientConfig.providers` names it in the routes and in\n * `onAuthorized`. Two keys may share an implementation (two Google Cloud projects, one Okta per\n * tenant) when their subjects are separate namespaces.\n */\nexport interface ProviderConfig {\n /** The implementation: `apple`, `google`, `github`, ... from `@shware/security/oauth2/provider`. */\n provider: Provider;\n /** The redirect flow's and One Tap's client. Required by those flows. */\n web?: WebClient;\n /**\n * Clients the native login accepts besides `web`. An app names its client with `client_id`;\n * without one, the only native client is used, or `web` when there is none.\n */\n native?: NativeClient[];\n}\n\nexport type ProvidersConfig = { [key: string]: ProviderConfig | undefined };\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n /** Providers by key; a function is called per request, for secrets read at request time. */\n providers: ProvidersConfig | (() => Promise<ProvidersConfig>);\n}\n"],"mappings":";;AAkKA,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -154,6 +154,11 @@ export interface StandardClaims {
154
154
  picture?: string;
155
155
  website?: string;
156
156
  email?: string;
157
+ /**
158
+ * Set only by providers that state it (Google, Apple, GitHub from the primary address's
159
+ * `verified` flag, X when it returns `confirmed_email`); Facebook leaves it unset. Trust the
160
+ * address only when this is `true`.
161
+ */
157
162
  email_verified?: boolean;
158
163
  gender?: 'female' | 'male' | (string & {});
159
164
  birthdate?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","names":[],"sources":["../../src/oauth2/types.ts"],"mappings":"A,iBACiB;EACf;EACA;EACA;;iBAGe;EACf;;EAEA;EACA;EACA,sBAAsB;;;;;;EAMtB,aAAa;;UAGL;EACR;EACA;EACA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;;EAEA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;;;;;EAKf;EACA;EACA;;;;;;EAMA,OAAO,KAAK;;EAEZ,aAAa;;iBAIE;;;;;EAKf;;;;;EAMA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;EAEA;GAEC;;iBAGc,kBAAkB;EACjC;;YAGU;EACV;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;;;;;;;;;;;EAYA,WAAW;;;iBAII;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAQe;EACf;;qBAGW;YASD,oBAAoB;iBAGf;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;;EAEF;;iBAGe,SAAS;EACxB,QAAQ;EACR,MAAM;;iBAGS;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;EAKA;;EAGA;;;;;EAMA;;EAGA;;EAGA,yBAAyB,QAAQ,iCAAiC;;EAGlE,4BAA4B,QAAQ,uBAAuB,QAAQ;;;;;;EAOnE,cAAc,OAAO,aAAa,SAAS,oBAAoB,QAAQ;;EAGvE,sBAAsB,QAAQ,uBAAuB,QAAQ;;EAG7D,eAAe,QAAQ,sBAAsB;EAE7C,qBACE,QAAQ,4BACL;IAAU,OAAO;IAAa,UAAU;;;iBAG9B;EACf;EACA,WAAW;;iBAGI,uBAAuB;;EAEtC,eACE,kBACA,qBACG;IAAU,OAAO;IAAa,UAAU;;;;;;;;YAQnC,0BAA0B,8BAA8B;;iBAGnD;EACf;;EAEA,eAAe;;EAEf;;EAEA;;;;;;iBAOe;EACf;;EAEA,eAAe;;;;;EAKf;;;;;;;;iBASe;;EAEf,UAAU;;EAEV,MAAM;;;;;EAKN,SAAS;;YAGC;GAAqB,cAAc;;iBAE9B;EACf;EACA;EACA;;EAEA,WAAW,yBAAyB,QAAQ"}
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../../src/oauth2/types.ts"],"mappings":"A,iBACiB;EACf;EACA;EACA;;iBAGe;EACf;;EAEA;EACA;EACA,sBAAsB;;;;;;EAMtB,aAAa;;UAGL;EACR;EACA;EACA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;;EAEA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;;;;;EAKf;EACA;EACA;;;;;;EAMA,OAAO,KAAK;;EAEZ,aAAa;;iBAIE;;;;;EAKf;;;;;EAMA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;EAEA;GAEC;;iBAGc,kBAAkB;EACjC;;YAGU;EACV;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;;;;;;;;;;;EAYA,WAAW;;;iBAII;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAQe;EACf;;qBAGW;YASD,oBAAoB;iBAGf;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;EAMA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;;EAEF;;iBAGe,SAAS;EACxB,QAAQ;EACR,MAAM;;iBAGS;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;EAKA;;EAGA;;;;;EAMA;;EAGA;;EAGA,yBAAyB,QAAQ,iCAAiC;;EAGlE,4BAA4B,QAAQ,uBAAuB,QAAQ;;;;;;EAOnE,cAAc,OAAO,aAAa,SAAS,oBAAoB,QAAQ;;EAGvE,sBAAsB,QAAQ,uBAAuB,QAAQ;;EAG7D,eAAe,QAAQ,sBAAsB;EAE7C,qBACE,QAAQ,4BACL;IAAU,OAAO;IAAa,UAAU;;;iBAG9B;EACf;EACA,WAAW;;iBAGI,uBAAuB;;EAEtC,eACE,kBACA,qBACG;IAAU,OAAO;IAAa,UAAU;;;;;;;;YAQnC,0BAA0B,8BAA8B;;iBAGnD;EACf;;EAEA,eAAe;;EAEf;;EAEA;;;;;;iBAOe;EACf;;EAEA,eAAe;;;;;EAKf;;;;;;;;iBASe;;EAEf,UAAU;;EAEV,MAAM;;;;;EAKN,SAAS;;YAGC;GAAqB,cAAc;;iBAE9B;EACf;EACA;EACA;;EAEA,WAAW,yBAAyB,QAAQ"}
@@ -154,6 +154,11 @@ export interface StandardClaims {
154
154
  picture?: string;
155
155
  website?: string;
156
156
  email?: string;
157
+ /**
158
+ * Set only by providers that state it (Google, Apple, GitHub from the primary address's
159
+ * `verified` flag, X when it returns `confirmed_email`); Facebook leaves it unset. Trust the
160
+ * address only when this is `true`.
161
+ */
157
162
  email_verified?: boolean;
158
163
  gender?: 'female' | 'male' | (string & {});
159
164
  birthdate?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/oauth2/types.ts"],"mappings":"A,iBACiB;EACf;EACA;EACA;;iBAGe;EACf;;EAEA;EACA;EACA,sBAAsB;;;;;;EAMtB,aAAa;;UAGL;EACR;EACA;EACA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;;EAEA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;;;;;EAKf;EACA;EACA;;;;;;EAMA,OAAO,KAAK;;EAEZ,aAAa;;iBAIE;;;;;EAKf;;;;;EAMA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;EAEA;GAEC;;iBAGc,kBAAkB;EACjC;;YAGU;EACV;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;;;;;;;;;;;EAYA,WAAW;;;iBAII;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAQe;EACf;;qBAGW;YASD,oBAAoB;iBAGf;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;;EAEF;;iBAGe,SAAS;EACxB,QAAQ;EACR,MAAM;;iBAGS;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;EAKA;;EAGA;;;;;EAMA;;EAGA;;EAGA,yBAAyB,QAAQ,iCAAiC;;EAGlE,4BAA4B,QAAQ,uBAAuB,QAAQ;;;;;;EAOnE,cAAc,OAAO,aAAa,SAAS,oBAAoB,QAAQ;;EAGvE,sBAAsB,QAAQ,uBAAuB,QAAQ;;EAG7D,eAAe,QAAQ,sBAAsB;EAE7C,qBACE,QAAQ,4BACL;IAAU,OAAO;IAAa,UAAU;;;iBAG9B;EACf;EACA,WAAW;;iBAGI,uBAAuB;;EAEtC,eACE,kBACA,qBACG;IAAU,OAAO;IAAa,UAAU;;;;;;;;YAQnC,0BAA0B,8BAA8B;;iBAGnD;EACf;;EAEA,eAAe;;EAEf;;EAEA;;;;;;iBAOe;EACf;;EAEA,eAAe;;;;;EAKf;;;;;;;;iBASe;;EAEf,UAAU;;EAEV,MAAM;;;;;EAKN,SAAS;;YAGC;GAAqB,cAAc;;iBAE9B;EACf;EACA;EACA;;EAEA,WAAW,yBAAyB,QAAQ"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/oauth2/types.ts"],"mappings":"A,iBACiB;EACf;EACA;EACA;;iBAGe;EACf;;EAEA;EACA;EACA,sBAAsB;;;;;;EAMtB,aAAa;;UAGL;EACR;EACA;EACA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;;EAEA;EACA;EACA,OAAO,KAAK;;iBAGG;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA;;iBAGe;;;;;EAKf;EACA;EACA;;;;;;EAMA,OAAO,KAAK;;EAEZ,aAAa;;iBAIE;;;;;EAKf;;;;;EAMA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;EAEA;GAEC;;iBAGc,kBAAkB;EACjC;;YAGU;EACV;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;;;;;;;;;;;EAYA,WAAW;;;iBAII;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAQe;EACf;;qBAGW;YASD,oBAAoB;iBAGf;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;EAMA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;IACA;IACA;IACA;;EAEF;;iBAGe,SAAS;EACxB,QAAQ;EACR,MAAM;;iBAGS;;EAEf;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;EAKA;;EAGA;;;;;EAMA;;EAGA;;EAGA,yBAAyB,QAAQ,iCAAiC;;EAGlE,4BAA4B,QAAQ,uBAAuB,QAAQ;;;;;;EAOnE,cAAc,OAAO,aAAa,SAAS,oBAAoB,QAAQ;;EAGvE,sBAAsB,QAAQ,uBAAuB,QAAQ;;EAG7D,eAAe,QAAQ,sBAAsB;EAE7C,qBACE,QAAQ,4BACL;IAAU,OAAO;IAAa,UAAU;;;iBAG9B;EACf;EACA,WAAW;;iBAGI,uBAAuB;;EAEtC,eACE,kBACA,qBACG;IAAU,OAAO;IAAa,UAAU;;;;;;;;YAQnC,0BAA0B,8BAA8B;;iBAGnD;EACf;;EAEA,eAAe;;EAEf;;EAEA;;;;;;iBAOe;EACf;;EAEA,eAAe;;;;;EAKf;;;;;;;;iBASe;;EAEf,UAAU;;EAEV,MAAM;;;;;EAKN,SAAS;;YAGC;GAAqB,cAAc;;iBAE9B;EACf;EACA;EACA;;EAEA,WAAW,yBAAyB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n /** Key of the provider in `OAuth2ClientConfig.providers` the flow was started for. */\n provider: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n /**\n * What the host attached when the flow started (`oauth2Authorization` options), kept on the\n * server and handed back to `onAuthorized` once the callback checks out. Never sent to the\n * provider, unlike `additionalParameters`. Absent on requests stored before this field existed.\n */\n attributes?: Record<string, string>;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n /** Absent for public clients (Google's installed-app clients, PKCE-only clients). */\n clientSecret?: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret?: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n /**\n * The configured client the app signs in as (its `client_id`, or the provider's default native\n * client). Id tokens must be issued to it: it is the only audience accepted.\n */\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n /**\n * The PKCE verifier for the code exchange, taken from `credentials.code_verifier`: the app made\n * the challenge and keeps the verifier, so the server only ever has the verifier. Providers\n * whose token endpoint takes `code_verifier` send it; the others ignore it.\n */\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n /** What the app posted, without `code_verifier`, which arrives as `pkce`. */\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n /**\n * The PKCE verifier (RFC 7636) for `code`, when the app sent a `code_challenge` with the\n * authorization request: 43-128 characters from `[A-Za-z0-9-._~]`. Sent on to the provider's\n * token endpoint, which refuses the code unless the verifier matches its challenge, so a code\n * intercepted on the redirect (a custom-scheme redirect any app can claim) is useless alone.\n * RFC 8252 §8.1 requires it for native apps, whose installed-app clients have no secret.\n */\n code_verifier?: string;\n id_token?: string;\n access_token?: string;\n /**\n * The user's name as the platform hands it to the app, for providers whose id token carries none\n * (Apple sends it on the first authorization only): iOS\n * `ASAuthorizationAppleIDCredential.fullName`, expo-apple-authentication's `credential.fullName`.\n * The snake_case fields above are OAuth / OIDC protocol parameters; this one is not, it is the\n * platform's object passed through unchanged, so it keeps the platform's own name and apps send\n * `fullName: credential.fullName` as is. Unsigned client input: the provider cleans each part and\n * ignores anything that is not a string. Parts with a standard claim become `given_name`,\n * `family_name`, `middle_name` and `nickname`; `namePrefix` / `nameSuffix` have none and are kept\n * in `userInfo.data.fullName` only.\n */\n fullName?: PersonNameComponents;\n};\n\n/** iOS `PersonNameComponents` as JSON; every part may be missing or `null`. */\nexport interface PersonNameComponents {\n givenName?: string | null;\n familyName?: string | null;\n middleName?: string | null;\n namePrefix?: string | null;\n nameSuffix?: string | null;\n nickname?: string | null;\n}\n\n/**\n * Fields of the authorization response besides `code` and `state` that a provider uses to fill in\n * the user info: Apple posts the user's name as `user` (JSON) with the first authorization only,\n * since its id token never carries one. Unsigned, so treated as untrusted input.\n */\nexport interface AuthorizationResponse {\n user?: string;\n}\n\nexport const OIDC_SCOPES = [\n 'openid',\n 'profile',\n 'email',\n 'phone',\n 'address',\n 'offline_access',\n] as const;\n\nexport type OidcScope = (typeof OIDC_SCOPES)[number];\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n email_verified?: boolean;\n gender?: 'female' | 'male' | (string & {});\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /**\n * step 3: get user info. `context.clientId` is the client the token was issued to, the only\n * audience an id token may carry; `context.response` holds what the authorization response\n * carried besides the code, for the providers that send user data there (Apple).\n */\n getUserInfo: (token: OAuth2Token, context: UserInfoContext) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface UserInfoContext {\n clientId: string;\n response?: AuthorizationResponse;\n}\n\nexport interface OneTapProvider extends Provider {\n /** Verifies a One Tap / FedCM credential, which must be issued to `clientId` (the web client). */\n getTokenInfo: (\n id_token: string,\n clientId: string\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\n/**\n * A client secret: a fixed string, or a function minting one for the client id it is called with\n * (Sign in with Apple's signed JWT, see `createAppleClientSecret`), called on every use so it can\n * cache and renew on its own.\n */\nexport type ClientSecret = string | ((clientId: string) => string | Promise<string>);\n\n/** The client a provider's server-side flows sign in as: the redirect flow and Google One Tap. */\nexport interface WebClient {\n clientId: string;\n /** Absent for a public client. */\n clientSecret?: ClientSecret;\n /** Default: `${baseUri}/login/oauth2/code/<provider key>`. */\n redirectUri?: string;\n /** Authorization scopes. Default: the provider's `defaultScope`. */\n scope?: string[];\n}\n\n/**\n * A client an app signs in as on the device (an iOS bundle id, a Google iOS / Android client) and\n * whose code or id token it posts to the native login route.\n */\nexport interface NativeClient {\n clientId: string;\n /** Absent for a public client (Google's installed-app clients have none). */\n clientSecret?: ClientSecret;\n /**\n * Redirect URI sent with the code exchange when the app sends no `redirect_uri` of its own.\n * Default: `${baseUri}/login/oauth2/code/<provider key>`.\n */\n redirectUri?: string;\n}\n\n/**\n * One identity source: every user it signs in has a `sub` unique within it, whichever client they\n * came through. The key in `OAuth2ClientConfig.providers` names it in the routes and in\n * `onAuthorized`. Two keys may share an implementation (two Google Cloud projects, one Okta per\n * tenant) when their subjects are separate namespaces.\n */\nexport interface ProviderConfig {\n /** The implementation: `apple`, `google`, `github`, ... from `@shware/security/oauth2/provider`. */\n provider: Provider;\n /** The redirect flow's and One Tap's client. Required by those flows. */\n web?: WebClient;\n /**\n * Clients the native login accepts besides `web`. An app names its client with `client_id`;\n * without one, the only native client is used, or `web` when there is none.\n */\n native?: NativeClient[];\n}\n\nexport type ProvidersConfig = { [key: string]: ProviderConfig | undefined };\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n /** Providers by key; a function is called per request, for secrets read at request time. */\n providers: ProvidersConfig | (() => Promise<ProvidersConfig>);\n}\n"],"mappings":";AAkKA,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/oauth2/types.ts"],"sourcesContent":["// reference: https://datatracker.ietf.org/doc/html/rfc7636\nexport interface PkceParameters {\n code_verifier: string;\n code_challenge: string;\n code_challenge_method: 'S256' | 'plain';\n}\n\nexport interface OAuth2AuthorizationRequest {\n state: string;\n /** Key of the provider in `OAuth2ClientConfig.providers` the flow was started for. */\n provider: string;\n authorizationRequestUri: string;\n additionalParameters: PkceParameters;\n /**\n * What the host attached when the flow started (`oauth2Authorization` options), kept on the\n * server and handed back to `onAuthorized` once the callback checks out. Never sent to the\n * provider, unlike `additionalParameters`. Absent on requests stored before this field existed.\n */\n attributes?: Record<string, string>;\n}\n\ninterface CreateAuthorizationUriParams {\n state: string;\n clientId: string;\n redirectUri: string;\n scope: string[] | undefined;\n pkce?: Omit<PkceParameters, 'code_verifier'>;\n}\n\nexport interface ExchangeCodeParams {\n code: string;\n clientId: string;\n /** Absent for public clients (Google's installed-app clients, PKCE-only clients). */\n clientSecret?: string;\n redirectUri: string;\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n}\n\nexport interface RefreshTokenParams {\n clientId: string;\n clientSecret?: string;\n refreshToken: string;\n}\n\nexport interface RevokeTokenParams {\n token: string;\n clientId: string;\n clientSecret?: string;\n}\n\nexport interface LoginOAuth2NativeParams {\n /**\n * The configured client the app signs in as (its `client_id`, or the provider's default native\n * client). Id tokens must be issued to it: it is the only audience accepted.\n */\n clientId: string;\n clientSecret?: string;\n redirectUri: string;\n /**\n * The PKCE verifier for the code exchange, taken from `credentials.code_verifier`: the app made\n * the challenge and keeps the verifier, so the server only ever has the verifier. Providers\n * whose token endpoint takes `code_verifier` send it; the others ignore it.\n */\n pkce?: Omit<PkceParameters, 'code_challenge' | 'code_challenge_method'>;\n /** What the app posted, without `code_verifier`, which arrives as `pkce`. */\n credentials: NativeCredential;\n}\n\n// reference: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\nexport interface OAuth2Token {\n /**\n * REQUIRED.\n * The access token issued by the authorization server.\n */\n access_token: string;\n\n /**\n * REQUIRED.\n * The type of the token issued as described in Section 7.1. Value is case insensitive.\n */\n token_type: 'Bearer' | 'bearer' | 'mac' | (string & {});\n\n /**\n * RECOMMENDED.\n * The lifetime in seconds of the access token. For example, the value \"3600\" denotes that the\n * access token will expire in one hour from the time the response was generated. If omitted, the\n * authorization server SHOULD provide the expiration time via other means or document the default\n * value.\n */\n expires_in?: number;\n\n /**\n * OPTIONAL.\n * The refresh token, which can be used to obtain new access tokens using the same authorization\n * grant as described in Section 6.\n */\n refresh_token?: string;\n\n /**\n * OPTIONAL.\n * If identical to the scope requested by the client; otherwise, REQUIRED. The scope of the\n * access token as described by Section 3.3.\n */\n scope?: string;\n\n id_token?: string;\n\n [key: string]: unknown;\n}\n\nexport interface OidcToken extends OAuth2Token {\n id_token: string;\n}\n\nexport type NativeCredential = {\n state?: string;\n code?: string;\n client_id?: string;\n redirect_uri?: string;\n /**\n * The PKCE verifier (RFC 7636) for `code`, when the app sent a `code_challenge` with the\n * authorization request: 43-128 characters from `[A-Za-z0-9-._~]`. Sent on to the provider's\n * token endpoint, which refuses the code unless the verifier matches its challenge, so a code\n * intercepted on the redirect (a custom-scheme redirect any app can claim) is useless alone.\n * RFC 8252 §8.1 requires it for native apps, whose installed-app clients have no secret.\n */\n code_verifier?: string;\n id_token?: string;\n access_token?: string;\n /**\n * The user's name as the platform hands it to the app, for providers whose id token carries none\n * (Apple sends it on the first authorization only): iOS\n * `ASAuthorizationAppleIDCredential.fullName`, expo-apple-authentication's `credential.fullName`.\n * The snake_case fields above are OAuth / OIDC protocol parameters; this one is not, it is the\n * platform's object passed through unchanged, so it keeps the platform's own name and apps send\n * `fullName: credential.fullName` as is. Unsigned client input: the provider cleans each part and\n * ignores anything that is not a string. Parts with a standard claim become `given_name`,\n * `family_name`, `middle_name` and `nickname`; `namePrefix` / `nameSuffix` have none and are kept\n * in `userInfo.data.fullName` only.\n */\n fullName?: PersonNameComponents;\n};\n\n/** iOS `PersonNameComponents` as JSON; every part may be missing or `null`. */\nexport interface PersonNameComponents {\n givenName?: string | null;\n familyName?: string | null;\n middleName?: string | null;\n namePrefix?: string | null;\n nameSuffix?: string | null;\n nickname?: string | null;\n}\n\n/**\n * Fields of the authorization response besides `code` and `state` that a provider uses to fill in\n * the user info: Apple posts the user's name as `user` (JSON) with the first authorization only,\n * since its id token never carries one. Unsigned, so treated as untrusted input.\n */\nexport interface AuthorizationResponse {\n user?: string;\n}\n\nexport const OIDC_SCOPES = [\n 'openid',\n 'profile',\n 'email',\n 'phone',\n 'address',\n 'offline_access',\n] as const;\n\nexport type OidcScope = (typeof OIDC_SCOPES)[number];\n\n// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\nexport interface StandardClaims {\n sub: string;\n name?: string;\n given_name?: string;\n family_name?: string;\n middle_name?: string;\n nickname?: string;\n preferred_username?: string;\n profile?: string;\n picture?: string;\n website?: string;\n email?: string;\n /**\n * Set only by providers that state it (Google, Apple, GitHub from the primary address's\n * `verified` flag, X when it returns `confirmed_email`); Facebook leaves it unset. Trust the\n * address only when this is `true`.\n */\n email_verified?: boolean;\n gender?: 'female' | 'male' | (string & {});\n birthdate?: string; // ISO 8601-1 [ISO8601‑1] YYYY-MM-DD format.\n zoneinfo?: string; // IANA Time Zone Database [IANA.time‑zones]\n locale?: string; // BCP47 [RFC5646] language tag.\n phone_number?: string; // E.164\n phone_number_verified?: boolean;\n address?: {\n formatted: string;\n street_address: string;\n locality: string;\n region: string;\n postal_code: string;\n country: string;\n }; // [RFC8259] JSON object\n updated_at?: number;\n}\n\nexport interface UserInfo<T = unknown> {\n claims: StandardClaims;\n data: T;\n}\n\nexport interface Provider {\n /** Authorization URI for the provider. */\n authorizationUri: string;\n\n /** Token URI for the provider. */\n tokenUri: string;\n\n /** Token revoke URI for the provider. */\n tokenRevokeUri?: string;\n\n /** Token refresh URI for the provider. */\n tokenRefreshUri?: string;\n\n /** User info URI for the provider. */\n userInfoUri?: string;\n\n /** User info authentication method for the provider. */\n userInfoAuthenticationMethod?: string;\n\n /**\n * Name of the attribute that will be used to extract the username from the call to 'userInfoUri'.\n * */\n userNameAttribute?: string;\n\n /** JWK set URI for the provider */\n jwkSetUri?: string;\n\n /**\n * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 Authorization\n * Server Metadata endpoint defined by RFC 8414.\n * */\n issuerUri?: string;\n\n /** Default scopes for the provider. */\n defaultScope: string[];\n\n /** step 1: generate authorization uri and redirect user to it */\n createAuthorizationUri: (params: CreateAuthorizationUriParams) => URL;\n\n /** step 2: exchange code for token */\n exchangeAuthorizationCode: (params: ExchangeCodeParams) => Promise<OAuth2Token>;\n\n /**\n * step 3: get user info. `context.clientId` is the client the token was issued to, the only\n * audience an id token may carry; `context.response` holds what the authorization response\n * carried besides the code, for the providers that send user data there (Apple).\n */\n getUserInfo: (token: OAuth2Token, context: UserInfoContext) => Promise<UserInfo>;\n\n /** others: refresh access token */\n refreshAccessToken?: (params: RefreshTokenParams) => Promise<OAuth2Token>;\n\n /** others: revoke token */\n revokeToken?: (params: RevokeTokenParams) => Promise<void>;\n\n loginOAuth2Native?: (\n params: LoginOAuth2NativeParams\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\nexport interface UserInfoContext {\n clientId: string;\n response?: AuthorizationResponse;\n}\n\nexport interface OneTapProvider extends Provider {\n /** Verifies a One Tap / FedCM credential, which must be issued to `clientId` (the web client). */\n getTokenInfo: (\n id_token: string,\n clientId: string\n ) => Promise<{ token: OAuth2Token; userInfo: UserInfo }>;\n}\n\n/**\n * A client secret: a fixed string, or a function minting one for the client id it is called with\n * (Sign in with Apple's signed JWT, see `createAppleClientSecret`), called on every use so it can\n * cache and renew on its own.\n */\nexport type ClientSecret = string | ((clientId: string) => string | Promise<string>);\n\n/** The client a provider's server-side flows sign in as: the redirect flow and Google One Tap. */\nexport interface WebClient {\n clientId: string;\n /** Absent for a public client. */\n clientSecret?: ClientSecret;\n /** Default: `${baseUri}/login/oauth2/code/<provider key>`. */\n redirectUri?: string;\n /** Authorization scopes. Default: the provider's `defaultScope`. */\n scope?: string[];\n}\n\n/**\n * A client an app signs in as on the device (an iOS bundle id, a Google iOS / Android client) and\n * whose code or id token it posts to the native login route.\n */\nexport interface NativeClient {\n clientId: string;\n /** Absent for a public client (Google's installed-app clients have none). */\n clientSecret?: ClientSecret;\n /**\n * Redirect URI sent with the code exchange when the app sends no `redirect_uri` of its own.\n * Default: `${baseUri}/login/oauth2/code/<provider key>`.\n */\n redirectUri?: string;\n}\n\n/**\n * One identity source: every user it signs in has a `sub` unique within it, whichever client they\n * came through. The key in `OAuth2ClientConfig.providers` names it in the routes and in\n * `onAuthorized`. Two keys may share an implementation (two Google Cloud projects, one Okta per\n * tenant) when their subjects are separate namespaces.\n */\nexport interface ProviderConfig {\n /** The implementation: `apple`, `google`, `github`, ... from `@shware/security/oauth2/provider`. */\n provider: Provider;\n /** The redirect flow's and One Tap's client. Required by those flows. */\n web?: WebClient;\n /**\n * Clients the native login accepts besides `web`. An app names its client with `client_id`;\n * without one, the only native client is used, or `web` when there is none.\n */\n native?: NativeClient[];\n}\n\nexport type ProvidersConfig = { [key: string]: ProviderConfig | undefined };\n\nexport interface OAuth2ClientConfig {\n baseUri: string;\n errorUri: string;\n successUri: string;\n /** Providers by key; a function is called per request, for secrets read at request time. */\n providers: ProvidersConfig | (() => Promise<ProvidersConfig>);\n}\n"],"mappings":";AAkKA,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;AACF"}