@shware/security 9.2.0 → 9.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/dist/auth/index.cjs +37 -15
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +4 -1
- package/dist/auth/index.d.cts.map +1 -1
- package/dist/auth/index.d.mts +4 -1
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +38 -16
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/respond.cjs +7 -2
- package/dist/auth/respond.cjs.map +1 -1
- package/dist/auth/respond.d.cts +5 -0
- package/dist/auth/respond.d.cts.map +1 -1
- package/dist/auth/respond.d.mts +5 -0
- package/dist/auth/respond.d.mts.map +1 -1
- package/dist/auth/respond.mjs +7 -2
- package/dist/auth/respond.mjs.map +1 -1
- package/dist/oauth2/provider/apple.cjs +1 -1
- package/dist/oauth2/provider/apple.cjs.map +1 -1
- package/dist/oauth2/provider/apple.d.cts.map +1 -1
- package/dist/oauth2/provider/apple.d.mts.map +1 -1
- package/dist/oauth2/provider/apple.mjs +1 -1
- package/dist/oauth2/provider/apple.mjs.map +1 -1
- package/dist/oauth2/provider/google.cjs +1 -1
- package/dist/oauth2/provider/google.cjs.map +1 -1
- package/dist/oauth2/provider/google.d.cts.map +1 -1
- package/dist/oauth2/provider/google.d.mts.map +1 -1
- package/dist/oauth2/provider/google.mjs +1 -1
- package/dist/oauth2/provider/google.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apple.d.mts","names":[],"sources":["../../../src/oauth2/provider/apple.ts"],"mappings":";;wBAiBgB,uBAAuB;
|
|
1
|
+
{"version":3,"file":"apple.d.mts","names":[],"sources":["../../../src/oauth2/provider/apple.ts"],"mappings":";;wBAiBgB,uBAAuB;qBAoF1B,OAAK;iBAID;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;;;;;;iBAOe,sBAAsB;;EAErC,OAAO;;EAEP,WAAW;;iBAKI,mBAAmB;EAClC;EACA;EACA;EACA;EACA;;iBAGe,8BAA8B;EAC7C;EACA;EACA;;iBAIe;EACf"}
|
|
@@ -64,7 +64,7 @@ function createAppleProvider() {
|
|
|
64
64
|
};
|
|
65
65
|
},
|
|
66
66
|
async loginOAuth2Native({ pkce: _, credentials, ...params }) {
|
|
67
|
-
|
|
67
|
+
if (!credentials.code) throw new OAuth2Error(400, "invalid_request", "code is required");
|
|
68
68
|
const { tokenUri } = this;
|
|
69
69
|
const { code } = credentials;
|
|
70
70
|
const response = await exchangeAuthorizationCode({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apple.mjs","names":[],"sources":["../../../src/oauth2/provider/apple.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport { type AppleUser, cleanPersonName, nameClaims, parseAppleUser } from '../name';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n PersonNameComponents,\n Provider,\n UserInfo,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** `iss` of every Apple id token (https://appleid.apple.com/.well-known/openid-configuration). */\nconst APPLE_ISSUER = 'https://appleid.apple.com';\n\n// ref: https://account.apple.com/.well-known/openid-configuration\nexport function createAppleProvider(): Provider {\n return {\n // important notice: response_mode=form_post is required for apple\n authorizationUri: 'https://appleid.apple.com/auth/authorize?response_mode=form_post',\n tokenUri: 'https://appleid.apple.com/auth/token',\n jwkSetUri: 'https://appleid.apple.com/auth/keys',\n userNameAttribute: 'sub',\n defaultScope: ['openid', 'name', 'email'],\n // The web flow's id token echoes the request's nonce; the app's carries the one bound into\n // ASAuthorizationAppleIDRequest, hashed or not (`nonce_supported`).\n nonce: { web: true, native: true },\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 AppleErrorResponse;\n throw new OAuth2Error(response.status, error);\n }\n return (await response.json()) as AppleToken;\n },\n async getUserInfo({ id_token }, { clientId, response }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n // The audience is the client the code was exchanged as: the bundle id for an app, the\n // Services ID for the web.\n const token = await verifyIdToken<AppleIdToken>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: APPLE_ISSUER,\n audience: clientId,\n });\n // Apple's id token never carries a name; on the first authorization of the web flow the\n // name comes in the form_post `user` instead, unsigned, so only its name parts are used\n // and the signed id token's email always wins over the one beside them.\n const user = parseAppleUser(response?.user);\n const data: AppleUserInfo = user ? { ...token, user } : token;\n return {\n data,\n claims: {\n sub: token.sub,\n email: token.email,\n email_verified: true,\n ...nameClaims({\n given_name: user?.name?.firstName,\n family_name: user?.name?.lastName,\n }),\n },\n };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n
|
|
1
|
+
{"version":3,"file":"apple.mjs","names":[],"sources":["../../../src/oauth2/provider/apple.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport { type AppleUser, cleanPersonName, nameClaims, parseAppleUser } from '../name';\nimport type {\n LoginOAuth2NativeParams,\n NativeCredential,\n OAuth2Token,\n PersonNameComponents,\n Provider,\n UserInfo,\n} from '../types';\nimport { createAuthorizationUri, exchangeAuthorizationCode, verifyIdToken } from './common';\n\n/** `iss` of every Apple id token (https://appleid.apple.com/.well-known/openid-configuration). */\nconst APPLE_ISSUER = 'https://appleid.apple.com';\n\n// ref: https://account.apple.com/.well-known/openid-configuration\nexport function createAppleProvider(): Provider {\n return {\n // important notice: response_mode=form_post is required for apple\n authorizationUri: 'https://appleid.apple.com/auth/authorize?response_mode=form_post',\n tokenUri: 'https://appleid.apple.com/auth/token',\n jwkSetUri: 'https://appleid.apple.com/auth/keys',\n userNameAttribute: 'sub',\n defaultScope: ['openid', 'name', 'email'],\n // The web flow's id token echoes the request's nonce; the app's carries the one bound into\n // ASAuthorizationAppleIDRequest, hashed or not (`nonce_supported`).\n nonce: { web: true, native: true },\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 AppleErrorResponse;\n throw new OAuth2Error(response.status, error);\n }\n return (await response.json()) as AppleToken;\n },\n async getUserInfo({ id_token }, { clientId, response }) {\n invariant(id_token, 'id_token is required');\n invariant(this.jwkSetUri, 'jwkSetUri is required');\n // The audience is the client the code was exchanged as: the bundle id for an app, the\n // Services ID for the web.\n const token = await verifyIdToken<AppleIdToken>(id_token, {\n jwkSetUri: this.jwkSetUri,\n issuer: APPLE_ISSUER,\n audience: clientId,\n });\n // Apple's id token never carries a name; on the first authorization of the web flow the\n // name comes in the form_post `user` instead, unsigned, so only its name parts are used\n // and the signed id token's email always wins over the one beside them.\n const user = parseAppleUser(response?.user);\n const data: AppleUserInfo = user ? { ...token, user } : token;\n return {\n data,\n claims: {\n sub: token.sub,\n email: token.email,\n email_verified: true,\n ...nameClaims({\n given_name: user?.name?.firstName,\n family_name: user?.name?.lastName,\n }),\n },\n };\n },\n async loginOAuth2Native({ pkce: _, credentials, ...params }: LoginOAuth2NativeParams) {\n // The app's mistake, a 400 like any other malformed login body, not a server fault.\n if (!credentials.code) throw new OAuth2Error(400, 'invalid_request', 'code is required');\n const { tokenUri } = this;\n const { code } = credentials;\n const response = await exchangeAuthorizationCode({ code, tokenUri, ...params });\n if (!response.ok) {\n const { error } = (await response.json()) as AppleErrorResponse;\n throw new OAuth2Error(response.status, error);\n }\n const token = (await response.json()) as AppleToken;\n const { data, claims } = await this.getUserInfo(token, { clientId: params.clientId });\n // The app gets the name from Apple on the first authorization only and passes it along in\n // the login body as the platform's `fullName`.\n const fullName = cleanPersonName(credentials.fullName);\n const name = nameClaims({\n given_name: fullName?.givenName ?? undefined,\n family_name: fullName?.familyName ?? undefined,\n middle_name: fullName?.middleName ?? undefined,\n nickname: fullName?.nickname ?? undefined,\n });\n const userInfo: UserInfo<AppleUserInfo> = {\n data: fullName ? { ...(data as AppleUserInfo), fullName } : (data as AppleUserInfo),\n claims: { ...claims, ...name },\n };\n return { token, userInfo };\n },\n };\n}\n\nexport const apple = createAppleProvider();\n\n// Claims of Apple's id token. It carries no name or picture, not even on the first authorization.\n// https://developer.apple.com/documentation/signinwithapple/authenticating-users-with-sign-in-with-apple\nexport interface AppleIdToken {\n iss: string;\n aud: string;\n exp: number;\n iat: number;\n sub: string;\n email: string;\n email_verified: true | 'true';\n is_private_email: boolean;\n /** Native flow, first authorization only: 0 unsupported, 1 unknown, 2 likely real. */\n real_user_status?: number;\n nonce?: string;\n nonce_supported?: boolean;\n}\n\n/**\n * `userInfo.data` of an Apple login: the id token's claims, plus the unsigned name the client\n * passed on the first authorization, cleaned (see `nameClaims` for the standard claims made of it).\n */\nexport interface AppleUserInfo extends AppleIdToken {\n /** Web flow: Apple's form_post `user`, as Apple names it. */\n user?: AppleUser;\n /** Native flow: the login body's `fullName`, which alone keeps `namePrefix` / `nameSuffix`. */\n fullName?: PersonNameComponents;\n}\n\n// https://developer.apple.com/documentation/devicemanagement/implementing-the-oauth2-authentication-user-enrollment-flow\n// https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens\nexport interface AppleToken extends OAuth2Token {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n id_token: string;\n}\n\nexport interface AppleNativeCredential extends NativeCredential {\n state: string;\n code: string;\n id_token: string;\n}\n\n// https://developer.apple.com/documentation/sign_in_with_apple/errorresponse\nexport interface AppleErrorResponse {\n error:\n | 'invalid_request'\n | 'invalid_client'\n | 'invalid_grant'\n | 'unauthorized_client'\n | 'unsupported_grant_type'\n | 'invalid_scope';\n}\n"],"mappings":";;;;;;AAcA,MAAM,eAAe;AAGrB,SAAgB,sBAAgC;CAC9C,OAAO;EAEL,kBAAkB;EAClB,UAAU;EACV,WAAW;EACX,mBAAmB;EACnB,cAAc;GAAC;GAAU;GAAQ;EAAO;EAGxC,OAAO;GAAE,KAAK;GAAM,QAAQ;EAAK;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,KAAK;GAC9C;GACA,OAAQ,MAAM,SAAS,KAAK;EAC9B;EACA,MAAM,YAAY,EAAE,YAAY,EAAE,UAAU,YAAY;GACtD,UAAU,UAAU,sBAAsB;GAC1C,UAAU,KAAK,WAAW,uBAAuB;GAGjD,MAAM,QAAQ,MAAM,cAA4B,UAAU;IACxD,WAAW,KAAK;IAChB,QAAQ;IACR,UAAU;GACZ,CAAC;GAID,MAAM,OAAO,eAAe,UAAU,IAAI;GAE1C,OAAO;IACL,MAF0B,OAAO;KAAE,GAAG;KAAO;IAAK,IAAI;IAGtD,QAAQ;KACN,KAAK,MAAM;KACX,OAAO,MAAM;KACb,gBAAgB;KAChB,GAAG,WAAW;MACZ,YAAY,MAAM,MAAM;MACxB,aAAa,MAAM,MAAM;KAC3B,CAAC;IACH;GACF;EACF;EACA,MAAM,kBAAkB,EAAE,MAAM,GAAG,aAAa,GAAG,UAAmC;GAEpF,IAAI,CAAC,YAAY,MAAM,MAAM,IAAI,YAAY,KAAK,mBAAmB,kBAAkB;GACvF,MAAM,EAAE,aAAa;GACrB,MAAM,EAAE,SAAS;GACjB,MAAM,WAAW,MAAM,0BAA0B;IAAE;IAAM;IAAU,GAAG;GAAO,CAAC;GAC9E,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,EAAE,UAAW,MAAM,SAAS,KAAK;IACvC,MAAM,IAAI,YAAY,SAAS,QAAQ,KAAK;GAC9C;GACA,MAAM,QAAS,MAAM,SAAS,KAAK;GACnC,MAAM,EAAE,MAAM,WAAW,MAAM,KAAK,YAAY,OAAO,EAAE,UAAU,OAAO,SAAS,CAAC;GAGpF,MAAM,WAAW,gBAAgB,YAAY,QAAQ;GACrD,MAAM,OAAO,WAAW;IACtB,YAAY,UAAU,aAAa,KAAA;IACnC,aAAa,UAAU,cAAc,KAAA;IACrC,aAAa,UAAU,cAAc,KAAA;IACrC,UAAU,UAAU,YAAY,KAAA;GAClC,CAAC;GAKD,OAAO;IAAE;IAAO,UAAA;KAHd,MAAM,WAAW;MAAE,GAAI;MAAwB;KAAS,IAAK;KAC7D,QAAQ;MAAE,GAAG;MAAQ,GAAG;KAAK;IAER;GAAE;EAC3B;CACF;AACF;AAEA,MAAa,QAAQ,oBAAoB"}
|
|
@@ -65,7 +65,7 @@ function createGoogleProvider() {
|
|
|
65
65
|
};
|
|
66
66
|
},
|
|
67
67
|
async loginOAuth2Native({ pkce, credentials, ...params }) {
|
|
68
|
-
|
|
68
|
+
if (!credentials.code) throw new require_oauth2_error.OAuth2Error(400, "invalid_request", "code is required");
|
|
69
69
|
const { code } = credentials;
|
|
70
70
|
const { tokenUri } = this;
|
|
71
71
|
const { redirect_uri = params.redirectUri } = credentials;
|
|
@@ -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/** 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 // The web flow and One Tap echo the nonce; an app signs in with a code it exchanges here,\n // bound by PKCE, and Google's native SDKs put no nonce in that id token.\n nonce: { web: true, native: false },\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
|
|
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 // The web flow and One Tap echo the nonce; an app signs in with a code it exchanges here,\n // bound by PKCE, and Google's native SDKs put no nonce in that id token.\n nonce: { web: true, native: false },\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 // The app's mistake, a 400 like any other malformed login body, not a server fault.\n if (!credentials.code) throw new OAuth2Error(400, 'invalid_request', '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. So is the PKCE verifier, when the app sent a\n // challenge: Google then refuses the code without it.\n const { redirect_uri = params.redirectUri } = credentials;\n const response = await exchangeAuthorizationCode({\n code,\n pkce,\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;EAGA,OAAO;GAAE,KAAK;GAAM,QAAQ;EAAM;EAClC,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,aAAa,GAAG,UAAmC;GAEjF,IAAI,CAAC,YAAY,MAAM,MAAM,IAAID,qBAAAA,YAAY,KAAK,mBAAmB,kBAAkB;GACvF,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAOrB,MAAM,EAAE,eAAe,OAAO,gBAAgB;GAC9C,MAAM,WAAW,MAAMD,+BAAAA,0BAA0B;IAC/C;IACA;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":";wBA0BgB,wBAAwB;
|
|
1
|
+
{"version":3,"file":"google.d.cts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";wBA0BgB,wBAAwB;qBA6F3B,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":";wBA0BgB,wBAAwB;
|
|
1
|
+
{"version":3,"file":"google.d.mts","names":[],"sources":["../../../src/oauth2/provider/google.ts"],"mappings":";wBA0BgB,wBAAwB;qBA6F3B,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"}
|
|
@@ -64,7 +64,7 @@ function createGoogleProvider() {
|
|
|
64
64
|
};
|
|
65
65
|
},
|
|
66
66
|
async loginOAuth2Native({ pkce, credentials, ...params }) {
|
|
67
|
-
|
|
67
|
+
if (!credentials.code) throw new OAuth2Error(400, "invalid_request", "code is required");
|
|
68
68
|
const { code } = credentials;
|
|
69
69
|
const { tokenUri } = this;
|
|
70
70
|
const { redirect_uri = params.redirectUri } = credentials;
|
|
@@ -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/** 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 // The web flow and One Tap echo the nonce; an app signs in with a code it exchanges here,\n // bound by PKCE, and Google's native SDKs put no nonce in that id token.\n nonce: { web: true, native: false },\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
|
|
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 // The web flow and One Tap echo the nonce; an app signs in with a code it exchanges here,\n // bound by PKCE, and Google's native SDKs put no nonce in that id token.\n nonce: { web: true, native: false },\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 // The app's mistake, a 400 like any other malformed login body, not a server fault.\n if (!credentials.code) throw new OAuth2Error(400, 'invalid_request', '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. So is the PKCE verifier, when the app sent a\n // challenge: Google then refuses the code without it.\n const { redirect_uri = params.redirectUri } = credentials;\n const response = await exchangeAuthorizationCode({\n code,\n pkce,\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;EAGA,OAAO;GAAE,KAAK;GAAM,QAAQ;EAAM;EAClC,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,aAAa,GAAG,UAAmC;GAEjF,IAAI,CAAC,YAAY,MAAM,MAAM,IAAI,YAAY,KAAK,mBAAmB,kBAAkB;GACvF,MAAM,EAAE,SAAS;GACjB,MAAM,EAAE,aAAa;GAOrB,MAAM,EAAE,eAAe,OAAO,gBAAgB;GAC9C,MAAM,WAAW,MAAM,0BAA0B;IAC/C;IACA;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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shware/security",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@shware/utils": "^2.0.
|
|
39
|
+
"@shware/utils": "^2.0.3",
|
|
40
40
|
"cookie": "^2.0.1",
|
|
41
41
|
"jose": "^6.2.13",
|
|
42
42
|
"path-to-regexp": "^8.4.2"
|