@shware/security 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +110 -99
  2. package/dist/auth/index.cjs +41 -34
  3. package/dist/auth/index.cjs.map +1 -1
  4. package/dist/auth/index.d.cts +6 -6
  5. package/dist/auth/index.d.cts.map +1 -1
  6. package/dist/auth/index.d.mts +6 -6
  7. package/dist/auth/index.d.mts.map +1 -1
  8. package/dist/auth/index.mjs +41 -34
  9. package/dist/auth/index.mjs.map +1 -1
  10. package/dist/auth/types.d.cts +12 -7
  11. package/dist/auth/types.d.cts.map +1 -1
  12. package/dist/auth/types.d.mts +12 -7
  13. package/dist/auth/types.d.mts.map +1 -1
  14. package/dist/index.cjs +2 -2
  15. package/dist/index.d.cts +2 -2
  16. package/dist/index.d.mts +2 -2
  17. package/dist/index.mjs +2 -2
  18. package/dist/oauth2/client.cjs +111 -48
  19. package/dist/oauth2/client.cjs.map +1 -1
  20. package/dist/oauth2/client.d.cts +51 -13
  21. package/dist/oauth2/client.d.cts.map +1 -1
  22. package/dist/oauth2/client.d.mts +51 -13
  23. package/dist/oauth2/client.d.mts.map +1 -1
  24. package/dist/oauth2/client.mjs +111 -48
  25. package/dist/oauth2/client.mjs.map +1 -1
  26. package/dist/oauth2/provider/apple-client-secret.cjs +3 -3
  27. package/dist/oauth2/provider/apple-client-secret.cjs.map +1 -1
  28. package/dist/oauth2/provider/apple-client-secret.d.cts +3 -3
  29. package/dist/oauth2/provider/apple-client-secret.d.mts +3 -3
  30. package/dist/oauth2/provider/apple-client-secret.mjs +3 -3
  31. package/dist/oauth2/provider/apple-client-secret.mjs.map +1 -1
  32. package/dist/oauth2/provider/apple.cjs +9 -3
  33. package/dist/oauth2/provider/apple.cjs.map +1 -1
  34. package/dist/oauth2/provider/apple.d.cts.map +1 -1
  35. package/dist/oauth2/provider/apple.d.mts.map +1 -1
  36. package/dist/oauth2/provider/apple.mjs +9 -3
  37. package/dist/oauth2/provider/apple.mjs.map +1 -1
  38. package/dist/oauth2/provider/common.cjs +13 -9
  39. package/dist/oauth2/provider/common.cjs.map +1 -1
  40. package/dist/oauth2/provider/common.d.cts +13 -2
  41. package/dist/oauth2/provider/common.d.cts.map +1 -1
  42. package/dist/oauth2/provider/common.d.mts +13 -2
  43. package/dist/oauth2/provider/common.d.mts.map +1 -1
  44. package/dist/oauth2/provider/common.mjs +13 -9
  45. package/dist/oauth2/provider/common.mjs.map +1 -1
  46. package/dist/oauth2/provider/facebook.cjs +59 -35
  47. package/dist/oauth2/provider/facebook.cjs.map +1 -1
  48. package/dist/oauth2/provider/facebook.d.cts.map +1 -1
  49. package/dist/oauth2/provider/facebook.d.mts.map +1 -1
  50. package/dist/oauth2/provider/facebook.mjs +59 -35
  51. package/dist/oauth2/provider/facebook.mjs.map +1 -1
  52. package/dist/oauth2/provider/google.cjs +31 -32
  53. package/dist/oauth2/provider/google.cjs.map +1 -1
  54. package/dist/oauth2/provider/google.d.cts.map +1 -1
  55. package/dist/oauth2/provider/google.d.mts.map +1 -1
  56. package/dist/oauth2/provider/google.mjs +31 -32
  57. package/dist/oauth2/provider/google.mjs.map +1 -1
  58. package/dist/oauth2/provider/index.cjs +1 -1
  59. package/dist/oauth2/provider/index.mjs +1 -1
  60. package/dist/oauth2/provider/wechat.cjs +1 -0
  61. package/dist/oauth2/provider/wechat.cjs.map +1 -1
  62. package/dist/oauth2/provider/wechat.d.cts.map +1 -1
  63. package/dist/oauth2/provider/wechat.d.mts.map +1 -1
  64. package/dist/oauth2/provider/wechat.mjs +1 -0
  65. package/dist/oauth2/provider/wechat.mjs.map +1 -1
  66. package/dist/oauth2/types.cjs.map +1 -1
  67. package/dist/oauth2/types.d.cts +69 -34
  68. package/dist/oauth2/types.d.cts.map +1 -1
  69. package/dist/oauth2/types.d.mts +69 -34
  70. package/dist/oauth2/types.d.mts.map +1 -1
  71. package/dist/oauth2/types.mjs.map +1 -1
  72. package/package.json +1 -1
@@ -1,8 +1,14 @@
1
1
  import { OAuth2Error } from "../error.mjs";
2
2
  import { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from "./common.mjs";
3
+ import { createHmac } from "node:crypto";
3
4
  import { invariant } from "@shware/utils";
4
5
  //#region src/oauth2/provider/facebook.ts
5
6
  /**
7
+ * `iss` of Limited Login id tokens, per https://limited.facebook.com/.well-known/openid-configuration/
8
+ * (their `aud` is the app id).
9
+ */
10
+ const FACEBOOK_ISSUER = "https://www.facebook.com";
11
+ /**
6
12
  * ref: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/
7
13
  * ref: https://developers.facebook.com/tools/explorer
8
14
  * */
@@ -32,10 +38,14 @@ function createFacebookProvider() {
32
38
  }
33
39
  return await response.json();
34
40
  },
35
- async getUserInfo({ id_token, access_token }) {
41
+ async getUserInfo({ id_token, access_token }, { clientId }) {
36
42
  invariant(this.jwkSetUri, "jwkSetUri is required");
37
43
  if (id_token) {
38
- const data = await verifyIdToken(id_token, this.jwkSetUri);
44
+ const data = await verifyIdToken(id_token, {
45
+ jwkSetUri: this.jwkSetUri,
46
+ issuer: FACEBOOK_ISSUER,
47
+ audience: clientId
48
+ });
39
49
  return {
40
50
  data,
41
51
  claims: {
@@ -49,45 +59,59 @@ function createFacebookProvider() {
49
59
  }
50
60
  invariant(access_token, "access_token is required");
51
61
  invariant(this.userInfoUri, "userInfoUri is required");
52
- const url = new URL(this.userInfoUri);
53
- const fields = [
54
- "id",
55
- "name",
56
- "email",
57
- "picture.width(320).height(320)"
58
- ];
59
- url.searchParams.append("access_token", access_token);
60
- url.searchParams.append("fields", fields.join(","));
61
- const response = await fetch(url.href, { headers: { Accept: "application/json" } });
62
- if (!response.ok) throw new OAuth2Error(response.status, "invalid_request", "Failed to fetch user info");
63
- const data = await response.json();
64
- return {
65
- data,
66
- claims: {
67
- sub: data.id,
68
- name: data.name,
69
- email: data.email,
70
- picture: data.picture.data.url,
71
- email_verified: data.email_verified
72
- }
73
- };
62
+ return fetchUserInfo(this.userInfoUri, access_token);
74
63
  },
75
- async loginOAuth2Native({ credentials }) {
76
- if (credentials.id_token || credentials.access_token) {
77
- const userInfo = await this.getUserInfo(credentials);
78
- return {
79
- token: {
80
- token_type: "bearer",
81
- access_token: credentials.access_token ?? "",
82
- id_token: credentials.id_token
83
- },
84
- userInfo
85
- };
64
+ async loginOAuth2Native({ clientId, clientSecret, credentials }) {
65
+ const { id_token, access_token } = credentials;
66
+ let userInfo;
67
+ if (id_token) userInfo = await this.getUserInfo({
68
+ token_type: "bearer",
69
+ access_token: "",
70
+ id_token
71
+ }, { clientId });
72
+ else if (access_token) {
73
+ if (!clientSecret) throw new OAuth2Error(400, "invalid_client", "access_token login needs the app secret");
74
+ invariant(this.userInfoUri, "userInfoUri is required");
75
+ const proof = createHmac("sha256", clientSecret).update(access_token).digest("hex");
76
+ userInfo = await fetchUserInfo(this.userInfoUri, access_token, proof);
86
77
  } else throw new OAuth2Error(400, "invalid_request", "id_token or access_token is required");
78
+ return {
79
+ token: {
80
+ token_type: "bearer",
81
+ access_token: access_token ?? "",
82
+ id_token
83
+ },
84
+ userInfo
85
+ };
87
86
  }
88
87
  };
89
88
  }
90
89
  const facebook = createFacebookProvider();
90
+ async function fetchUserInfo(userInfoUri, accessToken, appSecretProof) {
91
+ const url = new URL(userInfoUri);
92
+ const fields = [
93
+ "id",
94
+ "name",
95
+ "email",
96
+ "picture.width(320).height(320)"
97
+ ];
98
+ url.searchParams.append("access_token", accessToken);
99
+ url.searchParams.append("fields", fields.join(","));
100
+ if (appSecretProof) url.searchParams.append("appsecret_proof", appSecretProof);
101
+ const response = await fetch(url.href, { headers: { Accept: "application/json" } });
102
+ if (!response.ok) throw new OAuth2Error(response.status, "invalid_request", "Failed to fetch user info");
103
+ const data = await response.json();
104
+ return {
105
+ data,
106
+ claims: {
107
+ sub: data.id,
108
+ name: data.name,
109
+ email: data.email,
110
+ picture: data.picture.data.url,
111
+ email_verified: data.email_verified
112
+ }
113
+ };
114
+ }
91
115
  //#endregion
92
116
  export { createFacebookProvider, facebook };
93
117
 
@@ -1 +1 @@
1
- {"version":3,"file":"facebook.mjs","names":[],"sources":["../../../src/oauth2/provider/facebook.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { LoginOAuth2NativeParams, OAuth2Token, Provider } from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\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 }) {\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n if (id_token) {\n const data = await verifyIdToken<FacebookDecodedIdToken>(id_token, this.jwkSetUri);\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\n const url = new URL(this.userInfoUri);\n const fields = ['id', 'name', 'email', 'picture.width(320).height(320)'];\n url.searchParams.append('access_token', access_token);\n url.searchParams.append('fields', fields.join(','));\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 async loginOAuth2Native({ credentials }: LoginOAuth2NativeParams) {\n if (credentials.id_token || credentials.access_token) {\n const userInfo = await this.getUserInfo(credentials as unknown as OAuth2Token);\n const token: OAuth2Token = {\n token_type: 'bearer',\n access_token: credentials.access_token ?? '',\n id_token: credentials.id_token,\n };\n return { token, userInfo };\n } else {\n throw new OAuth2Error(400, 'invalid_request', 'id_token or access_token is required');\n }\n },\n };\n}\n\nexport const facebook = createFacebookProvider();\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":";;;;;;;;AASA,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;GAC5C,UAAU,KAAK,WAAW,uBAAuB;GACjD,IAAI,UAAU;IACZ,MAAM,OAAO,MAAM,cAAsC,UAAU,KAAK,SAAS;IACjF,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;GAErD,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,MAAM,SAAS;IAAC;IAAM;IAAQ;IAAS;GAAgC;GACvE,IAAI,aAAa,OAAO,gBAAgB,YAAY;GACpD,IAAI,aAAa,OAAO,UAAU,OAAO,KAAK,GAAG,CAAC;GAElD,MAAM,WAAW,MAAM,MAAM,IAAI,MAAM,EAAE,SAAA,EADvB,QAAQ,mBACqB,EAAE,CAAC;GAClD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,YAAY,SAAS,QAAQ,mBAAmB,2BAA2B;GAEvF,MAAM,OAAQ,MAAM,SAAS,KAAK;GAClC,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK;KACX,OAAO,KAAK;KACZ,SAAS,KAAK,QAAQ,KAAK;KAC3B,gBAAgB,KAAK;IACvB;GACF;EACF;EACA,MAAM,kBAAkB,EAAE,eAAwC;GAChE,IAAI,YAAY,YAAY,YAAY,cAAc;IACpD,MAAM,WAAW,MAAM,KAAK,YAAY,WAAqC;IAM7E,OAAO;KAAE,OAAA;MAJP,YAAY;MACZ,cAAc,YAAY,gBAAgB;MAC1C,UAAU,YAAY;KAEX;KAAG;IAAS;GAC3B,OACE,MAAM,IAAI,YAAY,KAAK,mBAAmB,sCAAsC;EAExF;CACF;AACF;AAEA,MAAa,WAAW,uBAAuB"}
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"}
@@ -3,9 +3,19 @@ const require_oauth2_error = require("../error.cjs");
3
3
  const require_oauth2_provider_common = require("./common.cjs");
4
4
  let _shware_utils = require("@shware/utils");
5
5
  //#region src/oauth2/provider/google.ts
6
- /** The Google Cloud project number every client id of a project starts with: `<number>-<hash>.apps.googleusercontent.com`. */
7
- function projectNumber(clientId) {
8
- return clientId.split("-", 1)[0];
6
+ /** Google signs id tokens with either `iss`, the URL or the bare host. */
7
+ const GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
8
+ function claims(data) {
9
+ return {
10
+ sub: data.sub,
11
+ name: data.name,
12
+ picture: data.picture,
13
+ email: data.email,
14
+ email_verified: data.email_verified,
15
+ given_name: data.given_name,
16
+ family_name: data.family_name,
17
+ locale: data.locale
18
+ };
9
19
  }
10
20
  function createGoogleProvider() {
11
21
  return {
@@ -37,35 +47,29 @@ function createGoogleProvider() {
37
47
  }
38
48
  return await response.json();
39
49
  },
40
- async getUserInfo({ id_token }) {
50
+ async getUserInfo({ id_token }, { clientId }) {
41
51
  (0, _shware_utils.invariant)(id_token, "id_token is required");
42
52
  (0, _shware_utils.invariant)(this.jwkSetUri, "jwkSetUri is required");
43
- const data = await require_oauth2_provider_common.verifyIdToken(id_token, this.jwkSetUri);
53
+ const data = await require_oauth2_provider_common.verifyIdToken(id_token, {
54
+ jwkSetUri: this.jwkSetUri,
55
+ issuer: GOOGLE_ISSUERS,
56
+ audience: clientId
57
+ });
44
58
  return {
45
59
  data,
46
- claims: {
47
- sub: data.sub,
48
- name: data.name,
49
- picture: data.picture,
50
- email: data.email,
51
- email_verified: data.email_verified,
52
- given_name: data.given_name,
53
- family_name: data.family_name,
54
- locale: data.locale
55
- }
60
+ claims: claims(data)
56
61
  };
57
62
  },
58
63
  async loginOAuth2Native({ pkce: _, credentials, ...params }) {
59
64
  (0, _shware_utils.invariant)(credentials.code, "code is required");
60
65
  const { code } = credentials;
61
66
  const { tokenUri } = this;
62
- const { client_id = params.clientId, redirect_uri = params.redirectUri } = credentials;
63
- if (projectNumber(client_id) !== projectNumber(params.clientId)) throw new require_oauth2_error.OAuth2Error(400, "invalid_client", "client_id belongs to another project");
67
+ const { redirect_uri = params.redirectUri } = credentials;
64
68
  const response = await require_oauth2_provider_common.exchangeAuthorizationCode({
65
69
  code,
66
70
  tokenUri,
67
- clientId: client_id,
68
- clientSecret: client_id === params.clientId ? params.clientSecret : "",
71
+ clientId: params.clientId,
72
+ clientSecret: params.clientSecret,
69
73
  redirectUri: redirect_uri
70
74
  });
71
75
  if (!response.ok) {
@@ -75,13 +79,17 @@ function createGoogleProvider() {
75
79
  const token = await response.json();
76
80
  return {
77
81
  token,
78
- userInfo: await this.getUserInfo(token)
82
+ userInfo: await this.getUserInfo(token, { clientId: params.clientId })
79
83
  };
80
84
  },
81
- async getTokenInfo(id_token) {
85
+ async getTokenInfo(id_token, clientId) {
82
86
  (0, _shware_utils.invariant)(id_token, "id_token is required");
83
87
  (0, _shware_utils.invariant)(this.jwkSetUri, "jwkSetUri is required");
84
- const data = await require_oauth2_provider_common.verifyIdToken(id_token, this.jwkSetUri);
88
+ const data = await require_oauth2_provider_common.verifyIdToken(id_token, {
89
+ jwkSetUri: this.jwkSetUri,
90
+ issuer: GOOGLE_ISSUERS,
91
+ audience: clientId
92
+ });
85
93
  return {
86
94
  token: {
87
95
  id_token,
@@ -93,16 +101,7 @@ function createGoogleProvider() {
93
101
  },
94
102
  userInfo: {
95
103
  data,
96
- claims: {
97
- sub: data.sub,
98
- name: data.name,
99
- picture: data.picture,
100
- email: data.email,
101
- email_verified: data.email_verified,
102
- given_name: data.given_name,
103
- family_name: data.family_name,
104
- locale: data.locale
105
- }
104
+ claims: claims(data)
106
105
  }
107
106
  };
108
107
  }
@@ -1 +1 @@
1
- {"version":3,"file":"google.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error","verifyIdToken"],"sources":["../../../src/oauth2/provider/google.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n OneTapProvider,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** The Google Cloud project number every client id of a project starts with: `<number>-<hash>.apps.googleusercontent.com`. */\nfunction projectNumber(clientId: string) {\n return clientId.split('-', 1)[0];\n}\n\nexport function createGoogleProvider(): OneTapProvider {\n return {\n authorizationUri: 'https://accounts.google.com/o/oauth2/v2/auth',\n tokenUri: 'https://oauth2.googleapis.com/token',\n userInfoUri: 'https://www.googleapis.com/oauth2/v3/userinfo',\n userNameAttribute: 'sub',\n jwkSetUri: 'https://www.googleapis.com/oauth2/v3/certs',\n // https://developers.google.com/identity/protocols/oauth2/scopes#oauth2\n defaultScope: [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ],\n createAuthorizationUri(params) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GoogleToken;\n },\n async getUserInfo({ id_token }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, this.jwkSetUri);\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: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n },\n };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n invariant(credentials.code, 'code is required');\n const { code } = credentials;\n const { tokenUri } = this;\n // An app signs in as its own installed-app client (the iOS / Android client id, custom-scheme\n // redirect), and Google only exchanges a code for the client it was issued to, so the exchange\n // uses the client the app names rather than the registration's web client. Installed-app\n // clients have no secret; the registration's is sent only when the exchange is for that very\n // client. Every client id carries its project number as prefix, which pins the app's client to\n // the registration's Google Cloud project — codes from a foreign project's client are refused.\n const { client_id = params.clientId, redirect_uri = params.redirectUri } = credentials;\n if (projectNumber(client_id) !== projectNumber(params.clientId)) {\n throw new OAuth2Error(400, 'invalid_client', 'client_id belongs to another project');\n }\n const response = await exchangeAuthorizationCode({\n code,\n tokenUri,\n clientId: client_id,\n clientSecret: client_id === params.clientId ? params.clientSecret : '',\n redirectUri: redirect_uri,\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n const token = (await response.json()) as GoogleToken;\n const userInfo = await this.getUserInfo(token);\n return { token, userInfo };\n },\n async getTokenInfo(id_token: string) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, this.jwkSetUri);\n return {\n token: {\n id_token,\n access_token: '',\n token_type: '',\n expires_in: 0,\n refresh_token: '',\n scope: '',\n },\n userInfo: {\n data,\n claims: {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n email_verified: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n },\n },\n };\n },\n };\n}\n\nexport const google = createGoogleProvider();\n\nexport interface GoogleUserInfo {\n aud: string;\n azp: string;\n email: string;\n email_verified: boolean;\n exp: number;\n family_name: string;\n given_name: string;\n hd?: string;\n iat: number;\n iss: string;\n jti?: string;\n locale?: string;\n name: string;\n nbf?: number;\n picture: string;\n sub: string;\n nonce?: string;\n}\n\nexport interface GoogleToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n id_token: string;\n scope: string;\n}\n\n// android,ios has different client_id and redirect_uri\nexport interface GoogleAppCredential extends NativeCredential {\n state: string;\n code: string;\n client_id: string;\n redirect_uri: string;\n}\n\nexport interface GoogleErrorResponse {\n error:\n | 'invalid_request'\n | 'invalid_client'\n | 'invalid_grant'\n | 'invalid_token'\n | 'invalid_scope'\n | 'unauthorized_client'\n | 'unsupported_grant_type'\n | 'access_denied'\n | 'server_error';\n error_description: string;\n}\n"],"mappings":";;;;;;AAWA,SAAS,cAAc,UAAkB;CACvC,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AAChC;AAEA,SAAgB,uBAAuC;CACrD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,WAAW;EAEX,cAAc;GACZ;GACA;GACA;EACF;EACA,uBAAuB,QAAQ;GAC7B,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,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,YAAY;GAC9B,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,sBAAsB;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAMC,+BAAAA,cAA8B,UAAU,KAAK,SAAS;GACzE,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK;KACX,SAAS,KAAK;KACd,OAAO,KAAK;KACZ,gBAAgB,KAAK;KACrB,YAAY,KAAK;KACjB,aAAa,KAAK;KAClB,QAAQ,KAAK;IACf;GACF;EACF;EACA,MAAM,kBAAkB,EAAE,MAAM,GAAG,aAAa,GAAG,UAAmC;GACpF,CAAA,GAAA,cAAA,UAAA,CAAU,YAAY,MAAM,kBAAkB;GAC9C,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAOrB,MAAM,EAAE,YAAY,OAAO,UAAU,eAAe,OAAO,gBAAgB;GAC3E,IAAI,cAAc,SAAS,MAAM,cAAc,OAAO,QAAQ,GAC5D,MAAM,IAAID,qBAAAA,YAAY,KAAK,kBAAkB,sCAAsC;GAErF,MAAM,WAAW,MAAMD,+BAAAA,0BAA0B;IAC/C;IACA;IACA,UAAU;IACV,cAAc,cAAc,OAAO,WAAW,OAAO,eAAe;IACpE,aAAa;GACf,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,MAAM,QAAS,MAAM,SAAS,KAAK;GAEnC,OAAO;IAAE;IAAO,UAAA,MADO,KAAK,YAAY,KAAK;GACpB;EAC3B;EACA,MAAM,aAAa,UAAkB;GACnC,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,sBAAsB;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAMC,+BAAAA,cAA8B,UAAU,KAAK,SAAS;GACzE,OAAO;IACL,OAAO;KACL;KACA,cAAc;KACd,YAAY;KACZ,YAAY;KACZ,eAAe;KACf,OAAO;IACT;IACA,UAAU;KACR;KACA,QAAQ;MACN,KAAK,KAAK;MACV,MAAM,KAAK;MACX,SAAS,KAAK;MACd,OAAO,KAAK;MACZ,gBAAgB,KAAK;MACrB,YAAY,KAAK;MACjB,aAAa,KAAK;MAClB,QAAQ,KAAK;KACf;IACF;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"google.cjs","names":["createAuthorizationUri","exchangeAuthorizationCode","OAuth2Error","verifyIdToken"],"sources":["../../../src/oauth2/provider/google.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n OneTapProvider,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** Google signs id tokens with either `iss`, the URL or the bare host. */\nconst GOOGLE_ISSUERS = ['https://accounts.google.com', 'accounts.google.com'];\n\nfunction claims(data: GoogleUserInfo) {\n return {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n email_verified: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n };\n}\n\nexport function createGoogleProvider(): OneTapProvider {\n return {\n authorizationUri: 'https://accounts.google.com/o/oauth2/v2/auth',\n tokenUri: 'https://oauth2.googleapis.com/token',\n userInfoUri: 'https://www.googleapis.com/oauth2/v3/userinfo',\n userNameAttribute: 'sub',\n jwkSetUri: 'https://www.googleapis.com/oauth2/v3/certs',\n // https://developers.google.com/identity/protocols/oauth2/scopes#oauth2\n defaultScope: [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ],\n createAuthorizationUri(params) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GoogleToken;\n },\n async getUserInfo({ id_token }, { clientId }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: GOOGLE_ISSUERS,\n audience: clientId,\n });\n return { data, claims: claims(data) };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n invariant(credentials.code, 'code is required');\n const { code } = credentials;\n const { tokenUri } = this;\n // An app signs in as its own installed-app client (the iOS / Android client id, custom-scheme\n // redirect), and Google only exchanges a code for the client it was issued to, so the exchange\n // runs as the client the app names, which the OAuth2 client has already checked is one of this\n // provider's configured clients. Installed-app clients have no secret. The redirect URI is the\n // one the app used, which only the app knows.\n const { redirect_uri = params.redirectUri } = credentials;\n const response = await exchangeAuthorizationCode({\n code,\n tokenUri,\n clientId: params.clientId,\n clientSecret: params.clientSecret,\n redirectUri: redirect_uri,\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n const token = (await response.json()) as GoogleToken;\n const userInfo = await this.getUserInfo(token, { clientId: params.clientId });\n return { token, userInfo };\n },\n async getTokenInfo(id_token: string, clientId: string) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n // One Tap and FedCM issue the credential to the web client.\n const data = await verifyIdToken<GoogleUserInfo>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: GOOGLE_ISSUERS,\n audience: clientId,\n });\n return {\n token: {\n id_token,\n access_token: '',\n token_type: '',\n expires_in: 0,\n refresh_token: '',\n scope: '',\n },\n userInfo: { data, claims: claims(data) },\n };\n },\n };\n}\n\nexport const google = createGoogleProvider();\n\nexport interface GoogleUserInfo {\n aud: string;\n azp: string;\n email: string;\n email_verified: boolean;\n exp: number;\n family_name: string;\n given_name: string;\n hd?: string;\n iat: number;\n iss: string;\n jti?: string;\n locale?: string;\n name: string;\n nbf?: number;\n picture: string;\n sub: string;\n nonce?: string;\n}\n\nexport interface GoogleToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n id_token: string;\n scope: string;\n}\n\n// android,ios has different client_id and redirect_uri\nexport interface GoogleAppCredential extends NativeCredential {\n state: string;\n code: string;\n client_id: string;\n redirect_uri: string;\n}\n\nexport interface GoogleErrorResponse {\n error:\n | 'invalid_request'\n | 'invalid_client'\n | 'invalid_grant'\n | 'invalid_token'\n | 'invalid_scope'\n | 'unauthorized_client'\n | 'unsupported_grant_type'\n | 'access_denied'\n | 'server_error';\n error_description: string;\n}\n"],"mappings":";;;;;;AAWA,MAAM,iBAAiB,CAAC,+BAA+B,qBAAqB;AAE5E,SAAS,OAAO,MAAsB;CACpC,OAAO;EACL,KAAK,KAAK;EACV,MAAM,KAAK;EACX,SAAS,KAAK;EACd,OAAO,KAAK;EACZ,gBAAgB,KAAK;EACrB,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,QAAQ,KAAK;CACf;AACF;AAEA,SAAgB,uBAAuC;CACrD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,WAAW;EAEX,cAAc;GACZ;GACA;GACA;EACF;EACA,uBAAuB,QAAQ;GAC7B,OAAOA,+BAAAA,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,WAAW,MAAMC,+BAAAA,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,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,YAAY,EAAE,YAAY;GAC5C,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,sBAAsB;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAMC,+BAAAA,cAA8B,UAAU;IACzD,WAAW,KAAK;IAChB,QAAQ;IACR,UAAU;GACZ,CAAC;GACD,OAAO;IAAE;IAAM,QAAQ,OAAO,IAAI;GAAE;EACtC;EACA,MAAM,kBAAkB,EAAE,MAAM,GAAG,aAAa,GAAG,UAAmC;GACpF,CAAA,GAAA,cAAA,UAAA,CAAU,YAAY,MAAM,kBAAkB;GAC9C,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAMrB,MAAM,EAAE,eAAe,OAAO,gBAAgB;GAC9C,MAAM,WAAW,MAAMF,+BAAAA,0BAA0B;IAC/C;IACA;IACA,UAAU,OAAO;IACjB,cAAc,OAAO;IACrB,aAAa;GACf,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,MAAM,QAAS,MAAM,SAAS,KAAK;GAEnC,OAAO;IAAE;IAAO,UAAA,MADO,KAAK,YAAY,OAAO,EAAE,UAAU,OAAO,SAAS,CAAC;GACnD;EAC3B;EACA,MAAM,aAAa,UAAkB,UAAkB;GACrD,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,sBAAsB;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,WAAW,uBAAuB;GAEjD,MAAM,OAAO,MAAMC,+BAAAA,cAA8B,UAAU;IACzD,WAAW,KAAK;IAChB,QAAQ;IACR,UAAU;GACZ,CAAC;GACD,OAAO;IACL,OAAO;KACL;KACA,cAAc;KACd,YAAY;KACZ,YAAY;KACZ,eAAe;KACf,OAAO;IACT;IACA,UAAU;KAAE;KAAM,QAAQ,OAAO,IAAI;IAAE;GACzC;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"google.d.cts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";;wBAegB,wBAAwB;qBA0G3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA;;iBAIe,4BAA4B;EAC3C;EACA;EACA;EACA;;iBAGe;EACf;EAUA"}
1
+ {"version":3,"file":"google.d.cts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";;wBA0BgB,wBAAwB;qBAuF3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA;;iBAIe,4BAA4B;EAC3C;EACA;EACA;EACA;;iBAGe;EACf;EAUA"}
@@ -1 +1 @@
1
- {"version":3,"file":"google.d.mts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";;wBAegB,wBAAwB;qBA0G3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA;;iBAIe,4BAA4B;EAC3C;EACA;EACA;EACA;;iBAGe;EACf;EAUA"}
1
+ {"version":3,"file":"google.d.mts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";;wBA0BgB,wBAAwB;qBAuF3B,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA;;iBAIe,4BAA4B;EAC3C;EACA;EACA;EACA;;iBAGe;EACf;EAUA"}
@@ -2,9 +2,19 @@ import { OAuth2Error } from "../error.mjs";
2
2
  import { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from "./common.mjs";
3
3
  import { invariant } from "@shware/utils";
4
4
  //#region src/oauth2/provider/google.ts
5
- /** The Google Cloud project number every client id of a project starts with: `<number>-<hash>.apps.googleusercontent.com`. */
6
- function projectNumber(clientId) {
7
- return clientId.split("-", 1)[0];
5
+ /** Google signs id tokens with either `iss`, the URL or the bare host. */
6
+ const GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
7
+ function claims(data) {
8
+ return {
9
+ sub: data.sub,
10
+ name: data.name,
11
+ picture: data.picture,
12
+ email: data.email,
13
+ email_verified: data.email_verified,
14
+ given_name: data.given_name,
15
+ family_name: data.family_name,
16
+ locale: data.locale
17
+ };
8
18
  }
9
19
  function createGoogleProvider() {
10
20
  return {
@@ -36,35 +46,29 @@ function createGoogleProvider() {
36
46
  }
37
47
  return await response.json();
38
48
  },
39
- async getUserInfo({ id_token }) {
49
+ async getUserInfo({ id_token }, { clientId }) {
40
50
  invariant(id_token, "id_token is required");
41
51
  invariant(this.jwkSetUri, "jwkSetUri is required");
42
- const data = await verifyIdToken(id_token, this.jwkSetUri);
52
+ const data = await verifyIdToken(id_token, {
53
+ jwkSetUri: this.jwkSetUri,
54
+ issuer: GOOGLE_ISSUERS,
55
+ audience: clientId
56
+ });
43
57
  return {
44
58
  data,
45
- claims: {
46
- sub: data.sub,
47
- name: data.name,
48
- picture: data.picture,
49
- email: data.email,
50
- email_verified: data.email_verified,
51
- given_name: data.given_name,
52
- family_name: data.family_name,
53
- locale: data.locale
54
- }
59
+ claims: claims(data)
55
60
  };
56
61
  },
57
62
  async loginOAuth2Native({ pkce: _, credentials, ...params }) {
58
63
  invariant(credentials.code, "code is required");
59
64
  const { code } = credentials;
60
65
  const { tokenUri } = this;
61
- const { client_id = params.clientId, redirect_uri = params.redirectUri } = credentials;
62
- if (projectNumber(client_id) !== projectNumber(params.clientId)) throw new OAuth2Error(400, "invalid_client", "client_id belongs to another project");
66
+ const { redirect_uri = params.redirectUri } = credentials;
63
67
  const response = await exchangeAuthorizationCode({
64
68
  code,
65
69
  tokenUri,
66
- clientId: client_id,
67
- clientSecret: client_id === params.clientId ? params.clientSecret : "",
70
+ clientId: params.clientId,
71
+ clientSecret: params.clientSecret,
68
72
  redirectUri: redirect_uri
69
73
  });
70
74
  if (!response.ok) {
@@ -74,13 +78,17 @@ function createGoogleProvider() {
74
78
  const token = await response.json();
75
79
  return {
76
80
  token,
77
- userInfo: await this.getUserInfo(token)
81
+ userInfo: await this.getUserInfo(token, { clientId: params.clientId })
78
82
  };
79
83
  },
80
- async getTokenInfo(id_token) {
84
+ async getTokenInfo(id_token, clientId) {
81
85
  invariant(id_token, "id_token is required");
82
86
  invariant(this.jwkSetUri, "jwkSetUri is required");
83
- const data = await verifyIdToken(id_token, this.jwkSetUri);
87
+ const data = await verifyIdToken(id_token, {
88
+ jwkSetUri: this.jwkSetUri,
89
+ issuer: GOOGLE_ISSUERS,
90
+ audience: clientId
91
+ });
84
92
  return {
85
93
  token: {
86
94
  id_token,
@@ -92,16 +100,7 @@ function createGoogleProvider() {
92
100
  },
93
101
  userInfo: {
94
102
  data,
95
- claims: {
96
- sub: data.sub,
97
- name: data.name,
98
- picture: data.picture,
99
- email: data.email,
100
- email_verified: data.email_verified,
101
- given_name: data.given_name,
102
- family_name: data.family_name,
103
- locale: data.locale
104
- }
103
+ claims: claims(data)
105
104
  }
106
105
  };
107
106
  }
@@ -1 +1 @@
1
- {"version":3,"file":"google.mjs","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n OneTapProvider,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** The Google Cloud project number every client id of a project starts with: `<number>-<hash>.apps.googleusercontent.com`. */\nfunction projectNumber(clientId: string) {\n return clientId.split('-', 1)[0];\n}\n\nexport function createGoogleProvider(): OneTapProvider {\n return {\n authorizationUri: 'https://accounts.google.com/o/oauth2/v2/auth',\n tokenUri: 'https://oauth2.googleapis.com/token',\n userInfoUri: 'https://www.googleapis.com/oauth2/v3/userinfo',\n userNameAttribute: 'sub',\n jwkSetUri: 'https://www.googleapis.com/oauth2/v3/certs',\n // https://developers.google.com/identity/protocols/oauth2/scopes#oauth2\n defaultScope: [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ],\n createAuthorizationUri(params) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GoogleToken;\n },\n async getUserInfo({ id_token }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, this.jwkSetUri);\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: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n },\n };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n invariant(credentials.code, 'code is required');\n const { code } = credentials;\n const { tokenUri } = this;\n // An app signs in as its own installed-app client (the iOS / Android client id, custom-scheme\n // redirect), and Google only exchanges a code for the client it was issued to, so the exchange\n // uses the client the app names rather than the registration's web client. Installed-app\n // clients have no secret; the registration's is sent only when the exchange is for that very\n // client. Every client id carries its project number as prefix, which pins the app's client to\n // the registration's Google Cloud project — codes from a foreign project's client are refused.\n const { client_id = params.clientId, redirect_uri = params.redirectUri } = credentials;\n if (projectNumber(client_id) !== projectNumber(params.clientId)) {\n throw new OAuth2Error(400, 'invalid_client', 'client_id belongs to another project');\n }\n const response = await exchangeAuthorizationCode({\n code,\n tokenUri,\n clientId: client_id,\n clientSecret: client_id === params.clientId ? params.clientSecret : '',\n redirectUri: redirect_uri,\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n const token = (await response.json()) as GoogleToken;\n const userInfo = await this.getUserInfo(token);\n return { token, userInfo };\n },\n async getTokenInfo(id_token: string) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, this.jwkSetUri);\n return {\n token: {\n id_token,\n access_token: '',\n token_type: '',\n expires_in: 0,\n refresh_token: '',\n scope: '',\n },\n userInfo: {\n data,\n claims: {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n email_verified: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n },\n },\n };\n },\n };\n}\n\nexport const google = createGoogleProvider();\n\nexport interface GoogleUserInfo {\n aud: string;\n azp: string;\n email: string;\n email_verified: boolean;\n exp: number;\n family_name: string;\n given_name: string;\n hd?: string;\n iat: number;\n iss: string;\n jti?: string;\n locale?: string;\n name: string;\n nbf?: number;\n picture: string;\n sub: string;\n nonce?: string;\n}\n\nexport interface GoogleToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n id_token: string;\n scope: string;\n}\n\n// android,ios has different client_id and redirect_uri\nexport interface GoogleAppCredential extends NativeCredential {\n state: string;\n code: string;\n client_id: string;\n redirect_uri: string;\n}\n\nexport interface GoogleErrorResponse {\n error:\n | 'invalid_request'\n | 'invalid_client'\n | 'invalid_grant'\n | 'invalid_token'\n | 'invalid_scope'\n | 'unauthorized_client'\n | 'unsupported_grant_type'\n | 'access_denied'\n | 'server_error';\n error_description: string;\n}\n"],"mappings":";;;;;AAWA,SAAS,cAAc,UAAkB;CACvC,OAAO,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC;AAChC;AAEA,SAAgB,uBAAuC;CACrD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,WAAW;EAEX,cAAc;GACZ;GACA;GACA;EACF;EACA,uBAAuB,QAAQ;GAC7B,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,WAAW,MAAM,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,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,YAAY;GAC9B,UAAU,UAAU,sBAAsB;GAC1C,UAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAM,cAA8B,UAAU,KAAK,SAAS;GACzE,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK;KACV,MAAM,KAAK;KACX,SAAS,KAAK;KACd,OAAO,KAAK;KACZ,gBAAgB,KAAK;KACrB,YAAY,KAAK;KACjB,aAAa,KAAK;KAClB,QAAQ,KAAK;IACf;GACF;EACF;EACA,MAAM,kBAAkB,EAAE,MAAM,GAAG,aAAa,GAAG,UAAmC;GACpF,UAAU,YAAY,MAAM,kBAAkB;GAC9C,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAOrB,MAAM,EAAE,YAAY,OAAO,UAAU,eAAe,OAAO,gBAAgB;GAC3E,IAAI,cAAc,SAAS,MAAM,cAAc,OAAO,QAAQ,GAC5D,MAAM,IAAI,YAAY,KAAK,kBAAkB,sCAAsC;GAErF,MAAM,WAAW,MAAM,0BAA0B;IAC/C;IACA;IACA,UAAU;IACV,cAAc,cAAc,OAAO,WAAW,OAAO,eAAe;IACpE,aAAa;GACf,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,MAAM,QAAS,MAAM,SAAS,KAAK;GAEnC,OAAO;IAAE;IAAO,UAAA,MADO,KAAK,YAAY,KAAK;GACpB;EAC3B;EACA,MAAM,aAAa,UAAkB;GACnC,UAAU,UAAU,sBAAsB;GAC1C,UAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAM,cAA8B,UAAU,KAAK,SAAS;GACzE,OAAO;IACL,OAAO;KACL;KACA,cAAc;KACd,YAAY;KACZ,YAAY;KACZ,eAAe;KACf,OAAO;IACT;IACA,UAAU;KACR;KACA,QAAQ;MACN,KAAK,KAAK;MACV,MAAM,KAAK;MACX,SAAS,KAAK;MACd,OAAO,KAAK;MACZ,gBAAgB,KAAK;MACrB,YAAY,KAAK;MACjB,aAAa,KAAK;MAClB,QAAQ,KAAK;KACf;IACF;GACF;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"google.mjs","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n OneTapProvider,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** Google signs id tokens with either `iss`, the URL or the bare host. */\nconst GOOGLE_ISSUERS = ['https://accounts.google.com', 'accounts.google.com'];\n\nfunction claims(data: GoogleUserInfo) {\n return {\n sub: data.sub,\n name: data.name,\n picture: data.picture,\n email: data.email,\n email_verified: data.email_verified,\n given_name: data.given_name,\n family_name: data.family_name,\n locale: data.locale,\n };\n}\n\nexport function createGoogleProvider(): OneTapProvider {\n return {\n authorizationUri: 'https://accounts.google.com/o/oauth2/v2/auth',\n tokenUri: 'https://oauth2.googleapis.com/token',\n userInfoUri: 'https://www.googleapis.com/oauth2/v3/userinfo',\n userNameAttribute: 'sub',\n jwkSetUri: 'https://www.googleapis.com/oauth2/v3/certs',\n // https://developers.google.com/identity/protocols/oauth2/scopes#oauth2\n defaultScope: [\n 'https://www.googleapis.com/auth/userinfo.email',\n 'https://www.googleapis.com/auth/userinfo.profile',\n 'openid',\n ],\n createAuthorizationUri(params) {\n return createAuthorizationUri({\n ...params,\n scope: params.scope ?? this.defaultScope,\n authorizationUri: this.authorizationUri,\n });\n },\n async exchangeAuthorizationCode(params) {\n const response = await exchangeAuthorizationCode({ ...params, tokenUri: this.tokenUri });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n return (await response.json()) as GoogleToken;\n },\n async getUserInfo({ id_token }, { clientId }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n const data = await verifyIdToken<GoogleUserInfo>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: GOOGLE_ISSUERS,\n audience: clientId,\n });\n return { data, claims: claims(data) };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n invariant(credentials.code, 'code is required');\n const { code } = credentials;\n const { tokenUri } = this;\n // An app signs in as its own installed-app client (the iOS / Android client id, custom-scheme\n // redirect), and Google only exchanges a code for the client it was issued to, so the exchange\n // runs as the client the app names, which the OAuth2 client has already checked is one of this\n // provider's configured clients. Installed-app clients have no secret. The redirect URI is the\n // one the app used, which only the app knows.\n const { redirect_uri = params.redirectUri } = credentials;\n const response = await exchangeAuthorizationCode({\n code,\n tokenUri,\n clientId: params.clientId,\n clientSecret: params.clientSecret,\n redirectUri: redirect_uri,\n });\n if (!response.ok) {\n const { error, error_description } = (await response.json()) as GoogleErrorResponse;\n throw new OAuth2Error(response.status, error, error_description);\n }\n const token = (await response.json()) as GoogleToken;\n const userInfo = await this.getUserInfo(token, { clientId: params.clientId });\n return { token, userInfo };\n },\n async getTokenInfo(id_token: string, clientId: string) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n // One Tap and FedCM issue the credential to the web client.\n const data = await verifyIdToken<GoogleUserInfo>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: GOOGLE_ISSUERS,\n audience: clientId,\n });\n return {\n token: {\n id_token,\n access_token: '',\n token_type: '',\n expires_in: 0,\n refresh_token: '',\n scope: '',\n },\n userInfo: { data, claims: claims(data) },\n };\n },\n };\n}\n\nexport const google = createGoogleProvider();\n\nexport interface GoogleUserInfo {\n aud: string;\n azp: string;\n email: string;\n email_verified: boolean;\n exp: number;\n family_name: string;\n given_name: string;\n hd?: string;\n iat: number;\n iss: string;\n jti?: string;\n locale?: string;\n name: string;\n nbf?: number;\n picture: string;\n sub: string;\n nonce?: string;\n}\n\nexport interface GoogleToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n id_token: string;\n scope: string;\n}\n\n// android,ios has different client_id and redirect_uri\nexport interface GoogleAppCredential extends NativeCredential {\n state: string;\n code: string;\n client_id: string;\n redirect_uri: string;\n}\n\nexport interface GoogleErrorResponse {\n error:\n | 'invalid_request'\n | 'invalid_client'\n | 'invalid_grant'\n | 'invalid_token'\n | 'invalid_scope'\n | 'unauthorized_client'\n | 'unsupported_grant_type'\n | 'access_denied'\n | 'server_error';\n error_description: string;\n}\n"],"mappings":";;;;;AAWA,MAAM,iBAAiB,CAAC,+BAA+B,qBAAqB;AAE5E,SAAS,OAAO,MAAsB;CACpC,OAAO;EACL,KAAK,KAAK;EACV,MAAM,KAAK;EACX,SAAS,KAAK;EACd,OAAO,KAAK;EACZ,gBAAgB,KAAK;EACrB,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,QAAQ,KAAK;CACf;AACF;AAEA,SAAgB,uBAAuC;CACrD,OAAO;EACL,kBAAkB;EAClB,UAAU;EACV,aAAa;EACb,mBAAmB;EACnB,WAAW;EAEX,cAAc;GACZ;GACA;GACA;EACF;EACA,uBAAuB,QAAQ;GAC7B,OAAO,uBAAuB;IAC5B,GAAG;IACH,OAAO,OAAO,SAAS,KAAK;IAC5B,kBAAkB,KAAK;GACzB,CAAC;EACH;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,WAAW,MAAM,0BAA0B;IAAE,GAAG;IAAQ,UAAU,KAAK;GAAS,CAAC;GACvF,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,YAAY,EAAE,YAAY;GAC5C,UAAU,UAAU,sBAAsB;GAC1C,UAAU,KAAK,WAAW,uBAAuB;GACjD,MAAM,OAAO,MAAM,cAA8B,UAAU;IACzD,WAAW,KAAK;IAChB,QAAQ;IACR,UAAU;GACZ,CAAC;GACD,OAAO;IAAE;IAAM,QAAQ,OAAO,IAAI;GAAE;EACtC;EACA,MAAM,kBAAkB,EAAE,MAAM,GAAG,aAAa,GAAG,UAAmC;GACpF,UAAU,YAAY,MAAM,kBAAkB;GAC9C,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAMrB,MAAM,EAAE,eAAe,OAAO,gBAAgB;GAC9C,MAAM,WAAW,MAAM,0BAA0B;IAC/C;IACA;IACA,UAAU,OAAO;IACjB,cAAc,OAAO;IACrB,aAAa;GACf,CAAC;GACD,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,OAAO,sBAAuB,MAAM,SAAS,KAAK;IAC1D,MAAM,IAAI,YAAY,SAAS,QAAQ,OAAO,iBAAiB;GACjE;GACA,MAAM,QAAS,MAAM,SAAS,KAAK;GAEnC,OAAO;IAAE;IAAO,UAAA,MADO,KAAK,YAAY,OAAO,EAAE,UAAU,OAAO,SAAS,CAAC;GACnD;EAC3B;EACA,MAAM,aAAa,UAAkB,UAAkB;GACrD,UAAU,UAAU,sBAAsB;GAC1C,UAAU,KAAK,WAAW,uBAAuB;GAEjD,MAAM,OAAO,MAAM,cAA8B,UAAU;IACzD,WAAW,KAAK;IAChB,QAAQ;IACR,UAAU;GACZ,CAAC;GACD,OAAO;IACL,OAAO;KACL;KACA,cAAc;KACd,YAAY;KACZ,YAAY;KACZ,eAAe;KACf,OAAO;IACT;IACA,UAAU;KAAE;KAAM,QAAQ,OAAO,IAAI;IAAE;GACzC;EACF;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_oauth2_provider_apple = require("./apple.cjs");
3
2
  const require_oauth2_provider_apple_client_secret = require("./apple-client-secret.cjs");
3
+ const require_oauth2_provider_apple = require("./apple.cjs");
4
4
  const require_oauth2_provider_facebook = require("./facebook.cjs");
5
5
  const require_oauth2_provider_github = require("./github.cjs");
6
6
  const require_oauth2_provider_google = require("./google.cjs");
@@ -1,5 +1,5 @@
1
- import { apple } from "./apple.mjs";
2
1
  import { createAppleClientSecret } from "./apple-client-secret.mjs";
2
+ import { apple } from "./apple.mjs";
3
3
  import { facebook } from "./facebook.mjs";
4
4
  import { github } from "./github.mjs";
5
5
  import { google } from "./google.mjs";
@@ -23,6 +23,7 @@ function createWechatProvider(options = { isWechatBrowser: false }) {
23
23
  return url;
24
24
  },
25
25
  async exchangeAuthorizationCode(params) {
26
+ (0, _shware_utils.invariant)(params.clientSecret, "clientSecret is required for wechat");
26
27
  const url = new URL(this.tokenUri);
27
28
  url.searchParams.set("appid", params.clientId);
28
29
  url.searchParams.set("secret", params.clientSecret);
@@ -1 +1 @@
1
- {"version":3,"file":"wechat.cjs","names":["OAuth2Error"],"sources":["../../../src/oauth2/provider/wechat.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\n\nexport type Options = { isWechatBrowser: boolean };\n\n/**\n * reference: https://developers.weixin.qq.com/doc/offiaccount/en/OA_Web_Apps/Wechat_webpage_authorization.html\n */\nexport function createWechatProvider(options: Options = { isWechatBrowser: false }): Provider {\n const { isWechatBrowser } = options;\n return {\n authorizationUri: isWechatBrowser\n ? 'https://open.weixin.qq.com/connect/oauth2/authorize'\n : 'https://open.weixin.qq.com/connect/qrconnect',\n tokenUri: 'https://api.weixin.qq.com/sns/oauth2/access_token',\n tokenRefreshUri: 'https://api.weixin.qq.com/sns/oauth2/refresh_token',\n userInfoUri: 'https://api.weixin.qq.com/sns/userinfo',\n defaultScope: [isWechatBrowser ? 'snsapi_userinfo' : 'snsapi_login'],\n createAuthorizationUri(params) {\n const url = new URL(this.authorizationUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('scope', (params.scope ?? this.defaultScope).join(','));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', params.state);\n return url;\n },\n async exchangeAuthorizationCode(params) {\n const url = new URL(this.tokenUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('secret', params.clientSecret);\n url.searchParams.set('code', params.code);\n url.searchParams.set('grant_type', 'authorization_code');\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n async getUserInfo({ access_token, openid }) {\n invariant(this.userInfoUri, 'userInfoUri is required');\n invariant(openid && typeof openid === 'string', 'openid is required');\n const url = new URL(this.userInfoUri);\n url.searchParams.set('access_token', access_token);\n url.searchParams.set('openid', openid);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n const data = (await response.json()) as WechatUserInfo;\n\n return {\n data,\n claims: {\n sub: data.unionid ?? data.openid,\n name: data.nickname,\n picture: `${data.headimgurl.split('/').slice(0, -1).join('/')}/0`,\n gender: data.sex === 1 ? 'male' : data.sex === 2 ? 'female' : undefined,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const url = new URL(this.tokenRefreshUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('grant_type', 'refresh_token');\n url.searchParams.set('refresh_token', params.refreshToken);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n };\n}\n\nexport const wechat = createWechatProvider();\n\nexport interface WechatUserInfo {\n openid: string;\n nickname: string;\n sex: 0 | 1 | 2; // 0: unknown, 1: male, 2: female\n province: string;\n city: string;\n country: string;\n /**\n * User's profile photo. The last numeric value represents the size of a square profile photo\n * (The value can be 0, 46, 64, 96, or 132. The value 0 represents a 640*640 square profile\n * photo). This parameter is left blank if a user has no profile photo. If the user changes the\n * profile photo, the URL of the original profile photo will expire.\n * e.g. http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46\n */\n headimgurl: string;\n privilege: string[];\n unionid?: string;\n}\n\nexport interface WechatToken extends OAuth2Token {\n access_token: string;\n expires_in: number;\n refresh_token: string;\n openid: string;\n scope: string;\n unionid?: string;\n}\n\ninterface WechatErrorResponse {\n errcode: number;\n errmsg: string;\n}\n"],"mappings":";;;;;;;AASA,SAAgB,qBAAqB,UAAmB,EAAE,iBAAiB,MAAM,GAAa;CAC5F,MAAM,EAAE,oBAAoB;CAC5B,OAAO;EACL,kBAAkB,kBACd,wDACA;EACJ,UAAU;EACV,iBAAiB;EACjB,aAAa;EACb,cAAc,CAAC,kBAAkB,oBAAoB,cAAc;EACnE,uBAAuB,QAAQ;GAC7B,MAAM,MAAM,IAAI,IAAI,KAAK,gBAAgB;GACzC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,SAAS,KAAK,aAAA,CAAc,KAAK,GAAG,CAAC;GAC3E,IAAI,aAAa,IAAI,gBAAgB,OAAO,WAAW;GACvD,IAAI,aAAa,IAAI,iBAAiB,MAAM;GAC5C,IAAI,aAAa,IAAI,SAAS,OAAO,KAAK;GAC1C,OAAO;EACT;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,MAAM,IAAI,IAAI,KAAK,QAAQ;GACjC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,YAAY;GAClD,IAAI,aAAa,IAAI,QAAQ,OAAO,IAAI;GACxC,IAAI,aAAa,IAAI,cAAc,oBAAoB;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,cAAc,UAAU;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,OAAO,WAAW,UAAU,oBAAoB;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,IAAI,aAAa,IAAI,gBAAgB,YAAY;GACjD,IAAI,aAAa,IAAI,UAAU,MAAM;GACrC,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK,WAAW,KAAK;KAC1B,MAAM,KAAK;KACX,SAAS,GAAG,KAAK,WAAW,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,EAAE;KAC9D,QAAQ,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,KAAA;IAChE;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,MAAM,IAAI,IAAI,KAAK,eAAe;GACxC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,cAAc,eAAe;GAClD,IAAI,aAAa,IAAI,iBAAiB,OAAO,YAAY;GACzD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"wechat.cjs","names":["OAuth2Error"],"sources":["../../../src/oauth2/provider/wechat.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\n\nexport type Options = { isWechatBrowser: boolean };\n\n/**\n * reference: https://developers.weixin.qq.com/doc/offiaccount/en/OA_Web_Apps/Wechat_webpage_authorization.html\n */\nexport function createWechatProvider(options: Options = { isWechatBrowser: false }): Provider {\n const { isWechatBrowser } = options;\n return {\n authorizationUri: isWechatBrowser\n ? 'https://open.weixin.qq.com/connect/oauth2/authorize'\n : 'https://open.weixin.qq.com/connect/qrconnect',\n tokenUri: 'https://api.weixin.qq.com/sns/oauth2/access_token',\n tokenRefreshUri: 'https://api.weixin.qq.com/sns/oauth2/refresh_token',\n userInfoUri: 'https://api.weixin.qq.com/sns/userinfo',\n defaultScope: [isWechatBrowser ? 'snsapi_userinfo' : 'snsapi_login'],\n createAuthorizationUri(params) {\n const url = new URL(this.authorizationUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('scope', (params.scope ?? this.defaultScope).join(','));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', params.state);\n return url;\n },\n async exchangeAuthorizationCode(params) {\n invariant(params.clientSecret, 'clientSecret is required for wechat');\n const url = new URL(this.tokenUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('secret', params.clientSecret);\n url.searchParams.set('code', params.code);\n url.searchParams.set('grant_type', 'authorization_code');\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n async getUserInfo({ access_token, openid }) {\n invariant(this.userInfoUri, 'userInfoUri is required');\n invariant(openid && typeof openid === 'string', 'openid is required');\n const url = new URL(this.userInfoUri);\n url.searchParams.set('access_token', access_token);\n url.searchParams.set('openid', openid);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n const data = (await response.json()) as WechatUserInfo;\n\n return {\n data,\n claims: {\n sub: data.unionid ?? data.openid,\n name: data.nickname,\n picture: `${data.headimgurl.split('/').slice(0, -1).join('/')}/0`,\n gender: data.sex === 1 ? 'male' : data.sex === 2 ? 'female' : undefined,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const url = new URL(this.tokenRefreshUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('grant_type', 'refresh_token');\n url.searchParams.set('refresh_token', params.refreshToken);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n };\n}\n\nexport const wechat = createWechatProvider();\n\nexport interface WechatUserInfo {\n openid: string;\n nickname: string;\n sex: 0 | 1 | 2; // 0: unknown, 1: male, 2: female\n province: string;\n city: string;\n country: string;\n /**\n * User's profile photo. The last numeric value represents the size of a square profile photo\n * (The value can be 0, 46, 64, 96, or 132. The value 0 represents a 640*640 square profile\n * photo). This parameter is left blank if a user has no profile photo. If the user changes the\n * profile photo, the URL of the original profile photo will expire.\n * e.g. http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46\n */\n headimgurl: string;\n privilege: string[];\n unionid?: string;\n}\n\nexport interface WechatToken extends OAuth2Token {\n access_token: string;\n expires_in: number;\n refresh_token: string;\n openid: string;\n scope: string;\n unionid?: string;\n}\n\ninterface WechatErrorResponse {\n errcode: number;\n errmsg: string;\n}\n"],"mappings":";;;;;;;AASA,SAAgB,qBAAqB,UAAmB,EAAE,iBAAiB,MAAM,GAAa;CAC5F,MAAM,EAAE,oBAAoB;CAC5B,OAAO;EACL,kBAAkB,kBACd,wDACA;EACJ,UAAU;EACV,iBAAiB;EACjB,aAAa;EACb,cAAc,CAAC,kBAAkB,oBAAoB,cAAc;EACnE,uBAAuB,QAAQ;GAC7B,MAAM,MAAM,IAAI,IAAI,KAAK,gBAAgB;GACzC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,SAAS,KAAK,aAAA,CAAc,KAAK,GAAG,CAAC;GAC3E,IAAI,aAAa,IAAI,gBAAgB,OAAO,WAAW;GACvD,IAAI,aAAa,IAAI,iBAAiB,MAAM;GAC5C,IAAI,aAAa,IAAI,SAAS,OAAO,KAAK;GAC1C,OAAO;EACT;EACA,MAAM,0BAA0B,QAAQ;GACtC,CAAA,GAAA,cAAA,UAAA,CAAU,OAAO,cAAc,qCAAqC;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,QAAQ;GACjC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,YAAY;GAClD,IAAI,aAAa,IAAI,QAAQ,OAAO,IAAI;GACxC,IAAI,aAAa,IAAI,cAAc,oBAAoB;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,cAAc,UAAU;GAC1C,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,aAAa,yBAAyB;GACrD,CAAA,GAAA,cAAA,UAAA,CAAU,UAAU,OAAO,WAAW,UAAU,oBAAoB;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,IAAI,aAAa,IAAI,gBAAgB,YAAY;GACjD,IAAI,aAAa,IAAI,UAAU,MAAM;GACrC,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK,WAAW,KAAK;KAC1B,MAAM,KAAK;KACX,SAAS,GAAG,KAAK,WAAW,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,EAAE;KAC9D,QAAQ,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,KAAA;IAChE;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,CAAA,GAAA,cAAA,UAAA,CAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,MAAM,IAAI,IAAI,KAAK,eAAe;GACxC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,cAAc,eAAe;GAClD,IAAI,aAAa,IAAI,iBAAiB,OAAO,YAAY;GACzD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAIA,qBAAAA,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"wechat.d.cts","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"mappings":";;YAIY;EAAY;;;;;wBAKR,qBAAqB,UAAS,UAAuC;qBAuExE,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"wechat.d.cts","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"mappings":";;YAIY;EAAY;;;;;wBAKR,qBAAqB,UAAS,UAAuC;qBAwExE,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"wechat.d.mts","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"mappings":";;YAIY;EAAY;;;;;wBAKR,qBAAqB,UAAS,UAAuC;qBAuExE,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA"}
1
+ {"version":3,"file":"wechat.d.mts","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"mappings":";;YAIY;EAAY;;;;;wBAKR,qBAAqB,UAAS,UAAuC;qBAwExE,QAAM;iBAEF;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;EAQA;EACA;EACA;;iBAGe,oBAAoB;EACnC;EACA;EACA;EACA;EACA;EACA"}
@@ -22,6 +22,7 @@ function createWechatProvider(options = { isWechatBrowser: false }) {
22
22
  return url;
23
23
  },
24
24
  async exchangeAuthorizationCode(params) {
25
+ invariant(params.clientSecret, "clientSecret is required for wechat");
25
26
  const url = new URL(this.tokenUri);
26
27
  url.searchParams.set("appid", params.clientId);
27
28
  url.searchParams.set("secret", params.clientSecret);
@@ -1 +1 @@
1
- {"version":3,"file":"wechat.mjs","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\n\nexport type Options = { isWechatBrowser: boolean };\n\n/**\n * reference: https://developers.weixin.qq.com/doc/offiaccount/en/OA_Web_Apps/Wechat_webpage_authorization.html\n */\nexport function createWechatProvider(options: Options = { isWechatBrowser: false }): Provider {\n const { isWechatBrowser } = options;\n return {\n authorizationUri: isWechatBrowser\n ? 'https://open.weixin.qq.com/connect/oauth2/authorize'\n : 'https://open.weixin.qq.com/connect/qrconnect',\n tokenUri: 'https://api.weixin.qq.com/sns/oauth2/access_token',\n tokenRefreshUri: 'https://api.weixin.qq.com/sns/oauth2/refresh_token',\n userInfoUri: 'https://api.weixin.qq.com/sns/userinfo',\n defaultScope: [isWechatBrowser ? 'snsapi_userinfo' : 'snsapi_login'],\n createAuthorizationUri(params) {\n const url = new URL(this.authorizationUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('scope', (params.scope ?? this.defaultScope).join(','));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', params.state);\n return url;\n },\n async exchangeAuthorizationCode(params) {\n const url = new URL(this.tokenUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('secret', params.clientSecret);\n url.searchParams.set('code', params.code);\n url.searchParams.set('grant_type', 'authorization_code');\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n async getUserInfo({ access_token, openid }) {\n invariant(this.userInfoUri, 'userInfoUri is required');\n invariant(openid && typeof openid === 'string', 'openid is required');\n const url = new URL(this.userInfoUri);\n url.searchParams.set('access_token', access_token);\n url.searchParams.set('openid', openid);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n const data = (await response.json()) as WechatUserInfo;\n\n return {\n data,\n claims: {\n sub: data.unionid ?? data.openid,\n name: data.nickname,\n picture: `${data.headimgurl.split('/').slice(0, -1).join('/')}/0`,\n gender: data.sex === 1 ? 'male' : data.sex === 2 ? 'female' : undefined,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const url = new URL(this.tokenRefreshUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('grant_type', 'refresh_token');\n url.searchParams.set('refresh_token', params.refreshToken);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n };\n}\n\nexport const wechat = createWechatProvider();\n\nexport interface WechatUserInfo {\n openid: string;\n nickname: string;\n sex: 0 | 1 | 2; // 0: unknown, 1: male, 2: female\n province: string;\n city: string;\n country: string;\n /**\n * User's profile photo. The last numeric value represents the size of a square profile photo\n * (The value can be 0, 46, 64, 96, or 132. The value 0 represents a 640*640 square profile\n * photo). This parameter is left blank if a user has no profile photo. If the user changes the\n * profile photo, the URL of the original profile photo will expire.\n * e.g. http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46\n */\n headimgurl: string;\n privilege: string[];\n unionid?: string;\n}\n\nexport interface WechatToken extends OAuth2Token {\n access_token: string;\n expires_in: number;\n refresh_token: string;\n openid: string;\n scope: string;\n unionid?: string;\n}\n\ninterface WechatErrorResponse {\n errcode: number;\n errmsg: string;\n}\n"],"mappings":";;;;;;AASA,SAAgB,qBAAqB,UAAmB,EAAE,iBAAiB,MAAM,GAAa;CAC5F,MAAM,EAAE,oBAAoB;CAC5B,OAAO;EACL,kBAAkB,kBACd,wDACA;EACJ,UAAU;EACV,iBAAiB;EACjB,aAAa;EACb,cAAc,CAAC,kBAAkB,oBAAoB,cAAc;EACnE,uBAAuB,QAAQ;GAC7B,MAAM,MAAM,IAAI,IAAI,KAAK,gBAAgB;GACzC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,SAAS,KAAK,aAAA,CAAc,KAAK,GAAG,CAAC;GAC3E,IAAI,aAAa,IAAI,gBAAgB,OAAO,WAAW;GACvD,IAAI,aAAa,IAAI,iBAAiB,MAAM;GAC5C,IAAI,aAAa,IAAI,SAAS,OAAO,KAAK;GAC1C,OAAO;EACT;EACA,MAAM,0BAA0B,QAAQ;GACtC,MAAM,MAAM,IAAI,IAAI,KAAK,QAAQ;GACjC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,YAAY;GAClD,IAAI,aAAa,IAAI,QAAQ,OAAO,IAAI;GACxC,IAAI,aAAa,IAAI,cAAc,oBAAoB;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,cAAc,UAAU;GAC1C,UAAU,KAAK,aAAa,yBAAyB;GACrD,UAAU,UAAU,OAAO,WAAW,UAAU,oBAAoB;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,IAAI,aAAa,IAAI,gBAAgB,YAAY;GACjD,IAAI,aAAa,IAAI,UAAU,MAAM;GACrC,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK,WAAW,KAAK;KAC1B,MAAM,KAAK;KACX,SAAS,GAAG,KAAK,WAAW,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,EAAE;KAC9D,QAAQ,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,KAAA;IAChE;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,UAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,MAAM,IAAI,IAAI,KAAK,eAAe;GACxC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,cAAc,eAAe;GAClD,IAAI,aAAa,IAAI,iBAAiB,OAAO,YAAY;GACzD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}
1
+ {"version":3,"file":"wechat.mjs","names":[],"sources":["../../../src/oauth2/provider/wechat.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\n\nexport type Options = { isWechatBrowser: boolean };\n\n/**\n * reference: https://developers.weixin.qq.com/doc/offiaccount/en/OA_Web_Apps/Wechat_webpage_authorization.html\n */\nexport function createWechatProvider(options: Options = { isWechatBrowser: false }): Provider {\n const { isWechatBrowser } = options;\n return {\n authorizationUri: isWechatBrowser\n ? 'https://open.weixin.qq.com/connect/oauth2/authorize'\n : 'https://open.weixin.qq.com/connect/qrconnect',\n tokenUri: 'https://api.weixin.qq.com/sns/oauth2/access_token',\n tokenRefreshUri: 'https://api.weixin.qq.com/sns/oauth2/refresh_token',\n userInfoUri: 'https://api.weixin.qq.com/sns/userinfo',\n defaultScope: [isWechatBrowser ? 'snsapi_userinfo' : 'snsapi_login'],\n createAuthorizationUri(params) {\n const url = new URL(this.authorizationUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('scope', (params.scope ?? this.defaultScope).join(','));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', params.state);\n return url;\n },\n async exchangeAuthorizationCode(params) {\n invariant(params.clientSecret, 'clientSecret is required for wechat');\n const url = new URL(this.tokenUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('secret', params.clientSecret);\n url.searchParams.set('code', params.code);\n url.searchParams.set('grant_type', 'authorization_code');\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n async getUserInfo({ access_token, openid }) {\n invariant(this.userInfoUri, 'userInfoUri is required');\n invariant(openid && typeof openid === 'string', 'openid is required');\n const url = new URL(this.userInfoUri);\n url.searchParams.set('access_token', access_token);\n url.searchParams.set('openid', openid);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n const data = (await response.json()) as WechatUserInfo;\n\n return {\n data,\n claims: {\n sub: data.unionid ?? data.openid,\n name: data.nickname,\n picture: `${data.headimgurl.split('/').slice(0, -1).join('/')}/0`,\n gender: data.sex === 1 ? 'male' : data.sex === 2 ? 'female' : undefined,\n },\n };\n },\n async refreshAccessToken(params) {\n invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n const url = new URL(this.tokenRefreshUri);\n url.searchParams.set('appid', params.clientId);\n url.searchParams.set('grant_type', 'refresh_token');\n url.searchParams.set('refresh_token', params.refreshToken);\n const response = await fetch(url.href);\n if (!response.ok) {\n const { errcode, errmsg } = (await response.json()) as WechatErrorResponse;\n throw new OAuth2Error(response.status, errmsg, `errcode: ${errcode}`);\n }\n return (await response.json()) as WechatToken;\n },\n };\n}\n\nexport const wechat = createWechatProvider();\n\nexport interface WechatUserInfo {\n openid: string;\n nickname: string;\n sex: 0 | 1 | 2; // 0: unknown, 1: male, 2: female\n province: string;\n city: string;\n country: string;\n /**\n * User's profile photo. The last numeric value represents the size of a square profile photo\n * (The value can be 0, 46, 64, 96, or 132. The value 0 represents a 640*640 square profile\n * photo). This parameter is left blank if a user has no profile photo. If the user changes the\n * profile photo, the URL of the original profile photo will expire.\n * e.g. http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46\n */\n headimgurl: string;\n privilege: string[];\n unionid?: string;\n}\n\nexport interface WechatToken extends OAuth2Token {\n access_token: string;\n expires_in: number;\n refresh_token: string;\n openid: string;\n scope: string;\n unionid?: string;\n}\n\ninterface WechatErrorResponse {\n errcode: number;\n errmsg: string;\n}\n"],"mappings":";;;;;;AASA,SAAgB,qBAAqB,UAAmB,EAAE,iBAAiB,MAAM,GAAa;CAC5F,MAAM,EAAE,oBAAoB;CAC5B,OAAO;EACL,kBAAkB,kBACd,wDACA;EACJ,UAAU;EACV,iBAAiB;EACjB,aAAa;EACb,cAAc,CAAC,kBAAkB,oBAAoB,cAAc;EACnE,uBAAuB,QAAQ;GAC7B,MAAM,MAAM,IAAI,IAAI,KAAK,gBAAgB;GACzC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,SAAS,KAAK,aAAA,CAAc,KAAK,GAAG,CAAC;GAC3E,IAAI,aAAa,IAAI,gBAAgB,OAAO,WAAW;GACvD,IAAI,aAAa,IAAI,iBAAiB,MAAM;GAC5C,IAAI,aAAa,IAAI,SAAS,OAAO,KAAK;GAC1C,OAAO;EACT;EACA,MAAM,0BAA0B,QAAQ;GACtC,UAAU,OAAO,cAAc,qCAAqC;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,QAAQ;GACjC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,UAAU,OAAO,YAAY;GAClD,IAAI,aAAa,IAAI,QAAQ,OAAO,IAAI;GACxC,IAAI,aAAa,IAAI,cAAc,oBAAoB;GACvD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,cAAc,UAAU;GAC1C,UAAU,KAAK,aAAa,yBAAyB;GACrD,UAAU,UAAU,OAAO,WAAW,UAAU,oBAAoB;GACpE,MAAM,MAAM,IAAI,IAAI,KAAK,WAAW;GACpC,IAAI,aAAa,IAAI,gBAAgB,YAAY;GACjD,IAAI,aAAa,IAAI,UAAU,MAAM;GACrC,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,MAAM,OAAQ,MAAM,SAAS,KAAK;GAElC,OAAO;IACL;IACA,QAAQ;KACN,KAAK,KAAK,WAAW,KAAK;KAC1B,MAAM,KAAK;KACX,SAAS,GAAG,KAAK,WAAW,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,EAAE;KAC9D,QAAQ,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,WAAW,KAAA;IAChE;GACF;EACF;EACA,MAAM,mBAAmB,QAAQ;GAC/B,UAAU,KAAK,iBAAiB,6BAA6B;GAC7D,MAAM,MAAM,IAAI,IAAI,KAAK,eAAe;GACxC,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ;GAC7C,IAAI,aAAa,IAAI,cAAc,eAAe;GAClD,IAAI,aAAa,IAAI,iBAAiB,OAAO,YAAY;GACzD,MAAM,WAAW,MAAM,MAAM,IAAI,IAAI;GACrC,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,SAAS,WAAY,MAAM,SAAS,KAAK;IACjD,MAAM,IAAI,YAAY,SAAS,QAAQ,QAAQ,YAAY,SAAS;GACtE;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;CACF;AACF;AAEA,MAAa,SAAS,qBAAqB"}