@absolutejs/auth 0.56.9 → 0.56.10
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/dist/cli/migrate.js.map +1 -1
- package/dist/index.js +61 -1
- package/dist/index.js.map +4 -4
- package/dist/oidc/index.d.ts +1 -0
- package/dist/oidc/index.js +40 -1
- package/dist/oidc/index.js.map +5 -4
- package/dist/oidc/operator.d.ts +14 -0
- package/dist/oidc/types.d.ts +4 -0
- package/dist/server.js +61 -1
- package/dist/server.js.map +4 -4
- package/package.json +1 -1
package/dist/cli/migrate.js.map
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"// @bun\n// src/constants.ts\nvar BASE64_BLOCK_SIZE = 4;\nvar NUM_GENERATOR_BYTES = 32;\n\n// src/graphqlQueries.ts\nvar anilistProfileQuery = `query {\n Viewer {\n id\n name\n about\n avatar {\n large\n medium\n }\n bannerImage\n siteUrl\n createdAt\n updatedAt\n donatorTier\n donatorBadge\n unreadNotificationCount\n options {\n titleLanguage\n displayAdultContent\n airingNotifications\n profileColor\n activityMergeTime\n staffNameLanguage\n }\n mediaListOptions {\n scoreFormat\n rowOrder\n animeList {\n sectionOrder\n customLists\n advancedScoringEnabled\n }\n mangaList {\n sectionOrder\n customLists\n advancedScoringEnabled\n }\n }\n statistics {\n anime {\n count\n meanScore\n minutesWatched\n episodesWatched\n }\n manga {\n count\n meanScore\n chaptersRead\n volumesRead\n }\n }\n favourites {\n anime {\n nodes {\n id\n title {\n romaji\n english\n }\n siteUrl\n }\n }\n manga {\n nodes {\n id\n title {\n romaji\n english\n }\n siteUrl\n }\n }\n characters {\n nodes {\n id\n name {\n full\n }\n image {\n large\n }\n }\n }\n staff {\n nodes {\n id\n name {\n full\n }\n image {\n large\n }\n }\n }\n studios {\n nodes {\n id\n name\n siteUrl\n }\n }\n }\n isFollower\n isFollowing\n }\n}`;\n\n// src/providers.ts\nvar defineProviders = (providers) => providers;\nvar defineProvider = (config) => config;\nvar providers = defineProviders({\n \"42\": {\n authorizationUrl: \"https://api.intra.42.fr/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.intra.42.fr/v2/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.intra.42.fr/oauth/token\"\n }\n },\n amazoncognito: {\n authorizationUrl: \"https://${domain}/oauth2/authorize\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `https://${config.domain}/oauth2/userInfo`\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `https://${config.domain}/oauth2/revoke`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.domain}/oauth2/token`\n }\n },\n anilist: {\n authorizationUrl: \"https://anilist.co/api/v2/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n body: {\n query: anilistProfileQuery\n },\n encoding: \"application/json\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n method: \"POST\",\n url: \"https://graphql.anilist.co\"\n },\n scopeRequired: false,\n subject: [\"data\", \"Viewer\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://anilist.co/api/v2/oauth/token\"\n }\n },\n apple: {\n authorizationUrl: \"https://appleid.apple.com/auth/authorize\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://appleid.apple.com/auth/userinfo\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://appleid.apple.com/auth/token\"\n }\n },\n atlassian: {\n authorizationUrl: \"https://auth.atlassian.com/authorize\",\n createAuthorizationURLSearchParams: {\n audience: \"api.atlassian.com\"\n },\n email: [\"email\"],\n fullName: [\"name\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"picture\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.atlassian.com/me\"\n },\n scopeRequired: true,\n subject: [\"account_id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://auth.atlassian.com/oauth/token\"\n }\n },\n attio: {\n authorizationUrl: \"https://app.attio.com/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.attio.com/v2/self\"\n },\n scopeRequired: true,\n subject: [\"workspace_id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://app.attio.com/oauth/token\"\n }\n },\n auth0: {\n authorizationUrl: (config) => `https://${config.domain}/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `https://${config.domain}/userinfo`\n },\n revocationRequest: {\n authIn: \"body\",\n body: new URLSearchParams({\n token_type_hint: \"refresh_token\"\n }),\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `https://${config.domain}/oauth/revoke`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.domain}/oauth/token`\n }\n },\n authentik: {\n authorizationUrl: (config) => `https://${config.baseURL}/oauth/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `https://${config.baseURL}/api/v3/user/`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.baseURL}/oauth/token`\n }\n },\n autodesk: {\n authorizationUrl: \"https://developer.api.autodesk.com/authentication/v2/authorize\",\n email: [\"email\"],\n familyName: [\"family_name\"],\n fullName: [\"name\"],\n givenName: [\"given_name\"],\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.userprofile.autodesk.com/userinfo\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://developer.api.autodesk.com/authentication/v2/token\"\n }\n },\n azureadb2c: {\n authorizationUrl: (config) => `https://${config.tenantSubdomain}.b2clogin.com/${config.tenantSubdomain}.onmicrosoft.com/${config.policy}/oauth2/v2.0/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n scopeRequired: false,\n subject: [\"sub\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.tenantSubdomain}.b2clogin.com/${config.tenantSubdomain}.onmicrosoft.com/${config.policy}/oauth2/v2.0/token`\n }\n },\n battlenet: {\n authorizationUrl: \"https://oauth.battle.net/authorize\",\n isOIDC: true,\n isRefreshable: false,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://oauth.battle.net/userinfo\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth.battle.net/token\"\n }\n },\n bitbucket: {\n authorizationUrl: \"https://bitbucket.org/site/oauth2/authorize\",\n fullName: [\"display_name\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"links\", \"avatar\", \"href\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.bitbucket.org/2.0/user\"\n },\n scopeRequired: false,\n subject: [\"uuid\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://bitbucket.org/site/oauth2/access_token\"\n }\n },\n box: {\n authorizationUrl: \"https://account.box.com/api/oauth2/authorize\",\n email: [\"login\"],\n fullName: [\"name\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"avatar_url\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.box.com/2.0/users/me\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://api.box.com/oauth2/revoke\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.box.com/oauth2/token\"\n }\n },\n bungie: {\n authorizationUrl: \"https://www.bungie.net/en/OAuth/Authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n headers: {\n \"X-API-Key\": \"<YOUR_API_KEY>\"\n },\n method: \"GET\",\n url: \"https://www.bungie.net/Platform/User/GetCurrentBungieNetUser\"\n },\n scopeRequired: false,\n subject: [\"Response\", \"membershipId\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.bungie.net/Platform/App/OAuth/token\"\n }\n },\n calendly: {\n authorizationUrl: \"https://auth.calendly.com/oauth/authorize\",\n email: [\"resource\", \"email\"],\n fullName: [\"resource\", \"name\"],\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.calendly.com/users/me\"\n },\n scopeRequired: false,\n subject: [\"resource\", \"uri\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://auth.calendly.com/oauth/token\"\n }\n },\n close: {\n authorizationUrl: \"https://app.close.com/oauth2/authorize/\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.close.com/api/v1/me/\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.close.com/oauth2/token/\"\n }\n },\n coinbase: {\n authorizationUrl: \"https://www.coinbase.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.coinbase.com/v2/user\"\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.coinbase.com/oauth/token\"\n }\n },\n discord: {\n authorizationUrl: \"https://discord.com/api/oauth2/authorize\",\n email: [\"email\"],\n isOIDC: true,\n isRefreshable: true,\n picture: [\"avatar\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://discord.com/api/users/@me\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://discord.com/api/oauth2/token\"\n }\n },\n donationalerts: {\n authorizationUrl: \"https://www.donationalerts.com/oauth/authorize\",\n email: [\"data\", \"email\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"data\", \"avatar\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://www.donationalerts.com/api/v1/user/oauth\"\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.donationalerts.com/oauth/token\"\n }\n },\n dribbble: {\n authorizationUrl: \"https://dribbble.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: false,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.dribbble.com/v2/user\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://dribbble.com/oauth/token\"\n }\n },\n dropbox: {\n authorizationUrl: \"https://www.dropbox.com/oauth2/authorize\",\n email: [\"email\"],\n familyName: [\"name\", \"surname\"],\n fullName: [\"name\", \"display_name\"],\n givenName: [\"name\", \"given_name\"],\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"POST\",\n url: \"https://api.dropboxapi.com/2/users/get_current_account\"\n },\n revocationRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://api.dropboxapi.com/2/auth/token/revoke\"\n },\n scopeRequired: false,\n subject: [\"account_id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.dropboxapi.com/oauth2/token\"\n }\n },\n epicgames: {\n authorizationUrl: \"https://www.epicgames.com/id/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.epicgames.dev/epic/oauth/v2/userInfo\"\n },\n scopeRequired: false,\n subject: [\"account_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.epicgames.dev/epic/oauth/v1/token\"\n }\n },\n etsy: {\n authorizationUrl: \"https://www.etsy.com/oauth/connect\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://openapi.etsy.com/v3/application/users/me\"\n },\n scopeRequired: false,\n subject: [\"results\", \"0\", \"user_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.etsy.com/v3/public/oauth/token\"\n }\n },\n facebook: {\n authorizationUrl: \"https://www.facebook.com/v16.0/dialog/oauth\",\n email: [\"email\"],\n familyName: [\"family_name\"],\n fullName: [\"name\"],\n givenName: [\"given_name\"],\n isOIDC: true,\n isRefreshable: false,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n method: \"GET\",\n searchParams: [[\"fields\", \"id,name,email,picture\"]],\n url: \"https://graph.facebook.com/me\"\n },\n scopeRequired: false,\n subject: [\"sub\"],\n subjectBySource: {\n idToken: [\"sub\"],\n profile: [\"id\"]\n },\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://graph.facebook.com/v16.0/oauth/access_token\"\n }\n },\n figma: {\n authorizationUrl: \"https://www.figma.com/oauth\",\n email: [\"email\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"img_url\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.figma.com/v1/me\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.figma.com/v1/oauth/token\"\n }\n },\n gitea: {\n authorizationUrl: (config) => `${config.baseURL}/login/oauth/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `${config.baseURL}/api/v1/user`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `${config.baseURL}/login/oauth/access_token`\n }\n },\n github: {\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n email: [\"email\"],\n isOIDC: false,\n isRefreshable: false,\n picture: [\"avatar_url\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.github.com/user\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://github.com/login/oauth/access_token\"\n }\n },\n gitlab: {\n authorizationUrl: (config) => `${config.baseURL}/oauth/authorize`,\n email: [\"email\"],\n fullName: [\"name\"],\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://gitlab.com/api/v4/user\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `${config.baseURL}/oauth/revoke`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `${config.baseURL}/oauth/token`\n }\n },\n gohighlevel: {\n authorizationUrl: \"https://marketplace.gohighlevel.com/v2/oauth/chooselocation\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n headers: {\n Version: \"2021-07-28\"\n },\n method: \"GET\",\n url: \"https://services.leadconnectorhq.com/users/me\"\n },\n scopeRequired: true,\n subject: [\"locationId\"],\n subjectBySource: {\n tokenResponse: [\"locationId\"]\n },\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://services.leadconnectorhq.com/oauth/token\"\n }\n },\n google: {\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n email: [\"email\"],\n familyName: [\"family_name\"],\n fullName: [\"name\"],\n givenName: [\"given_name\"],\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://openidconnect.googleapis.com/v1/userinfo\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://oauth2.googleapis.com/revoke\"\n },\n scopeRequired: true,\n subject: [\"sub\"],\n subjectBySource: {\n idToken: [\"sub\"],\n profile: [\"sub\"]\n },\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth2.googleapis.com/token\"\n }\n },\n hubspot: {\n authorizationUrl: \"https://app.hubspot.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"path\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.hubapi.com/oauth/v1/access-tokens\"\n },\n scopeRequired: true,\n subject: [\"hub_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.hubapi.com/oauth/v1/token\"\n }\n },\n intuit: {\n authorizationUrl: \"https://appcenter.intuit.com/connect/oauth2\",\n isOIDC: true,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => config.environment === \"production\" ? \"https://accounts.platform.intuit.com/v1/openid_connect/userinfo\" : \"https://sandbox-accounts.platform.intuit.com/v1/openid_connect/userinfo\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n headers: (config) => ({\n Authorization: `Basic ${encodeBase64(`${config.clientId}:${config.clientSecret}`)}`\n }),\n tokenParamName: \"token\",\n url: \"https://developer.api.intuit.com/v2/oauth2/tokens/revoke\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer\"\n }\n },\n kakao: {\n authorizationUrl: \"https://kauth.kakao.com/oauth/authorize\",\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://kapi.kakao.com/v2/user/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://kauth.kakao.com/oauth/token\"\n }\n },\n keycloak: {\n authorizationUrl: (config) => `${config.realmURL}/protocol/openid-connect/auth`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.kick.com/v1/user\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `${config.realmURL}/protocol/openid-connect/revoke`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `${config.realmURL}/protocol/openid-connect/token`\n }\n },\n kick: {\n authorizationUrl: \"https://id.kick.com/oauth/authorize\",\n email: [\"data\", \"email\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"data\", \"profile_picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.kick.com/public/v1/users\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://id.kick.com/oauth/revoke\"\n },\n scopeRequired: true,\n subject: [\"data\", \"user_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://id.kick.com/oauth/token\"\n }\n },\n lichess: {\n authorizationUrl: \"https://lichess.org/oauth/authorize\",\n isOIDC: false,\n isRefreshable: false,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://lichess.org/api/account\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://lichess.org/api/token\"\n }\n },\n line: {\n authorizationUrl: \"https://access.line.me/oauth2/v2.1/authorize\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.line.me/v2/profile\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.line.me/oauth2/v2.1/token\"\n }\n },\n linear: {\n authorizationUrl: \"https://linear.app/oauth/authorize\",\n isOIDC: false,\n isRefreshable: false,\n profileRequest: {\n authIn: \"header\",\n body: {\n query: `query { viewer { id name } }`\n },\n encoding: \"application/json\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n method: \"POST\",\n url: \"https://api.linear.app/graphql\"\n },\n revocationRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://api.linear.app/oauth/revoke\"\n },\n scopeRequired: false,\n subject: [\"data\", \"viewer\", \"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.linear.app/oauth/token\"\n }\n },\n linkedin: {\n authorizationUrl: \"https://www.linkedin.com/oauth/v2/authorization\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.linkedin.com/v2/me\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.linkedin.com/oauth/v2/accessToken\"\n }\n },\n mastodon: {\n authorizationUrl: (config) => `${config.baseURL}/oauth/authorize`,\n isOIDC: false,\n isRefreshable: false,\n picture: [\"avatar\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `${config.baseURL}/api/v1/accounts/verify_credentials`\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `${config.baseURL}/oauth/revoke`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `${config.baseURL}/oauth/token`\n }\n },\n mercadolibre: {\n authorizationUrl: \"https://auth.mercadolibre.com/authorization\",\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.mercadolibre.com/users/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.mercadolibre.com/oauth/token\"\n }\n },\n mercadopago: {\n authorizationUrl: \"https://auth.mercadopago.com/authorization\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.mercadopago.com/v1/users/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.mercadopago.com/oauth/token\"\n }\n },\n microsoftentraexternalid: {\n authorizationUrl: (config) => `https://${config.tenantSubdomain}.ciamlogin.com/${config.tenantId}/oauth2/v2.0/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n scopeRequired: false,\n subject: [\"sub\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.tenantSubdomain}.ciamlogin.com/${config.tenantId}/oauth2/v2.0/token`\n }\n },\n microsoftentraid: {\n authorizationUrl: (config) => `https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://graph.microsoft.com/oidc/userinfo\"\n },\n scopeRequired: false,\n subject: [\"sub\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/token`\n }\n },\n monday: {\n authorizationUrl: \"https://auth.monday.com/oauth2/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n body: {\n query: \"query { me { id name email } }\"\n },\n encoding: \"application/json\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n method: \"POST\",\n url: \"https://api.monday.com/v2\"\n },\n scopeRequired: true,\n subject: [\"data\", \"me\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://auth.monday.com/oauth2/token\"\n }\n },\n myanimelist: {\n authorizationUrl: \"https://myanimelist.net/v1/oauth2/authorize\",\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"plain\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.myanimelist.net/v2/users/@me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://myanimelist.net/v1/oauth2/token\"\n }\n },\n naver: {\n authorizationUrl: \"https://nid.naver.com/oauth2.0/authorize\",\n email: [\"response\", \"email\"],\n fullName: [\"response\", \"name\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"response\", \"profile_image\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://openapi.naver.com/v1/nid/me\"\n },\n scopeRequired: false,\n subject: [\"response\", \"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://nid.naver.com/oauth2.0/token\"\n }\n },\n notion: {\n authorizationUrl: \"https://api.notion.com/v1/oauth/authorize\",\n email: [\"bot\", \"owner\", \"user\", \"person\", \"email\"],\n isOIDC: false,\n isRefreshable: false,\n picture: [\"bot\", \"owner\", \"user\", \"avatar_url\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n headers: {\n \"Notion-Version\": \"2022-06-28\"\n },\n method: \"GET\",\n url: \"https://api.notion.com/v1/users/me\"\n },\n scopeRequired: false,\n subject: [\"bot\", \"owner\", \"user\", \"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://api.notion.com/v1/oauth/token\"\n }\n },\n okta: {\n authorizationUrl: (config) => `https://${config.domain}/oauth2/default/v1/authorize`,\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `https://${config.domain}/oauth2/default/v1/userinfo`\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: (config) => `https://${config.domain}/oauth2/default/v1/revoke`\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.domain}/oauth2/default/v1/token`\n }\n },\n onspark: {\n authorizationUrl: \"https://onspark.com/oauth2/authorize\",\n email: [\"email\"],\n familyName: [\"family_name\"],\n fullName: [\"name\"],\n givenName: [\"given_name\"],\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://onspark.com/oauth2/userinfo\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n tokenParamName: \"token\",\n url: \"https://onspark.com/oauth2/revoke\"\n },\n scopeRequired: true,\n subject: [\"sub\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://onspark.com/oauth2/token\"\n }\n },\n osu: {\n authorizationUrl: \"https://osu.ppy.sh/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n picture: [\"avatar_url\"],\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://osu.ppy.sh/api/v2/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://osu.ppy.sh/oauth/token\"\n }\n },\n patreon: {\n authorizationUrl: \"https://www.patreon.com/oauth2/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://www.patreon.com/api/oauth2/v2/identity\"\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.patreon.com/api/oauth2/token\"\n }\n },\n pipedrive: {\n authorizationUrl: \"https://oauth.pipedrive.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.pipedrive.com/v1/users/me\"\n },\n scopeRequired: true,\n subject: [\"data\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth.pipedrive.com/oauth/token\"\n }\n },\n polar: {\n authorizationUrl: \"https://polar.sh/oauth2/authorize\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.polar.sh/v1/oauth2/userinfo\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://api.polar.sh/v1/oauth2/revoke\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.polar.sh/v1/oauth2/token\"\n }\n },\n polaraccesslink: {\n authorizationUrl: \"https://flow.polar.com/oauth2/authorization\",\n isOIDC: false,\n isRefreshable: false,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://www.polaraccesslink.com/v3/users/me\"\n },\n scopeRequired: false,\n subject: [\"x_user_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"header\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://polarremote.com/v2/oauth2/token\"\n }\n },\n polarteampro: {\n authorizationUrl: \"https://auth.polar.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://www.polaraccesslink.com/v3/users/<USER_ID>\"\n },\n scopeRequired: false,\n subject: [\"x_user_id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"header\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://auth.polar.com/oauth/token\"\n }\n },\n reddit: {\n authorizationUrl: \"https://www.reddit.com/api/v1/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://oauth.reddit.com/api/v1/me\"\n },\n revocationRequest: {\n authIn: \"header\",\n body: new URLSearchParams({\n token_type_hint: \"refresh_token\"\n }),\n encoding: \"application/json\",\n url: \"https://www.reddit.com/api/v1/revoke_token\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"header\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.reddit.com/api/v1/access_token\"\n }\n },\n roblox: {\n authorizationUrl: \"https://apis.roblox.com/oauth/v1/authorize\",\n isOIDC: true,\n isRefreshable: true,\n picture: [\"picture\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://apis.roblox.com/oauth/v1/userinfo\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://apis.roblox.com/oauth/v1/token\"\n }\n },\n salesforce: {\n authorizationUrl: \"https://login.salesforce.com/services/oauth2/authorize\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://login.salesforce.com/services/oauth2/userinfo\"\n },\n revocationRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://login.salesforce.com/services/oauth2/revoke\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://login.salesforce.com/services/oauth2/token\"\n }\n },\n shikimori: {\n authorizationUrl: \"https://shikimori.org/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://shikimori.one/api/users/whoami\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://shikimori.org/oauth/token\"\n }\n },\n slack: {\n authorizationUrl: \"https://slack.com/openid/connect/authorize\",\n isOIDC: true,\n isRefreshable: true,\n profileRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://slack.com/api/users.identity\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://slack.com/api/auth.revoke\"\n },\n scopeRequired: true,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://slack.com/api/openid.connect.token\"\n }\n },\n slackuser: {\n accessTokenPath: [\"authed_user\", \"access_token\"],\n authorizationUrl: \"https://slack.com/oauth/v2/authorize\",\n isOIDC: false,\n isRefreshable: false,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"POST\",\n url: \"https://slack.com/api/auth.test\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://slack.com/api/auth.revoke\"\n },\n scopeParamName: \"user_scope\",\n scopeRequired: true,\n subject: [\"user_id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://slack.com/api/oauth.v2.access\"\n }\n },\n spotify: {\n authorizationUrl: \"https://accounts.spotify.com/authorize\",\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.spotify.com/v1/me\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://accounts.spotify.com/api/token\"\n }\n },\n startgg: {\n authorizationUrl: \"https://start.gg/oauth/authorize\",\n email: [\"data\", \"currentUser\", \"email\"],\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n body: {\n query: `query { currentUser { id slug email player { gamerTag } } }`\n },\n encoding: \"application/json\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n method: \"POST\",\n url: \"https://api.start.gg/gql/alpha\"\n },\n scopeRequired: false,\n subject: [\"data\", \"currentUser\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.start.gg/oauth/access_token\"\n }\n },\n strava: {\n authorizationUrl: \"https://www.strava.com/oauth/authorize\",\n familyName: [\"lastname\"],\n givenName: [\"firstname\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"profile\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://www.strava.com/api/v3/athlete\"\n },\n revocationRequest: {\n authIn: \"query\",\n body: new URLSearchParams({\n token_type_hint: \"access_token\"\n }),\n encoding: \"application/json\",\n tokenParamName: \"access_token\",\n url: \"https://www.strava.com/oauth/deauthorize\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://www.strava.com/oauth/token\"\n }\n },\n synology: {\n authorizationUrl: (config) => `${config.baseURL}/webman/sso/SSOOauth.cgi?client_id=${config.clientId}&response_type=code&redirect_uri=${config.redirectUri}`,\n isOIDC: false,\n isRefreshable: false,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `${config.baseURL}/webman/sso/SSOUserInfo.cgi?client_id=${config.clientId}&access_token=${config.accessToken}`\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `${config.baseURL}/webman/sso/SSOAccessToken.cgi?client_id=${config.clientId}&client_secret=${config.clientSecret}`\n }\n },\n tiktok: {\n authorizationUrl: \"https://www.tiktok.com/v2/auth/authorize\",\n createAuthorizationURLSearchParams: (config) => ({\n client_key: config.clientId\n }),\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://open.douyin.com/oauth/userinfo\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"token\",\n url: \"https://open.tiktokapis.com/v2/oauth/revoke/\"\n },\n scopeRequired: false,\n subject: [\"data\", \"open_id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://open.tiktokapis.com/v2/oauth/token/\"\n }\n },\n tiltify: {\n authorizationUrl: \"https://v5api.tiltify.com/oauth/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://v5api.tiltify.com/api/public/current-user\"\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://v5api.tiltify.com/oauth/token\"\n }\n },\n tumblr: {\n authorizationUrl: \"https://www.tumblr.com/oauth2/authorize\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.tumblr.com/v2/user/info\"\n },\n scopeRequired: false,\n subject: [\"response\", \"user\", \"name\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.tumblr.com/v2/oauth2/token\"\n }\n },\n twitch: {\n authorizationUrl: \"https://id.twitch.tv/oauth2/authorize\",\n isOIDC: true,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n headers: (config) => ({\n \"Client-Id\": config.clientId\n }),\n method: \"GET\",\n url: \"https://api.twitch.tv/helix/users\"\n },\n revocationRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n headers: (config) => ({\n \"Client-Id\": config.clientId\n }),\n tokenParamName: \"token\",\n url: \"https://id.twitch.tv/oauth2/revoke\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://id.twitch.tv/oauth2/token\"\n }\n },\n twitter: {\n authorizationUrl: \"https://twitter.com/i/oauth2/authorize\",\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.twitter.com/2/users/me\"\n },\n revocationRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://api.twitter.com/2/oauth2/revoke\"\n },\n scopeRequired: false,\n subject: [\"data\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.twitter.com/2/oauth2/token\"\n }\n },\n vk: {\n authorizationUrl: \"https://oauth.vk.com/authorize\",\n isOIDC: false,\n isRefreshable: false,\n profileRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.vk.com/method/users.get\"\n },\n scopeRequired: false,\n subject: [\"response\", \"0\", \"id\"],\n subjectType: \"number\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth.vk.com/access_token\"\n }\n },\n withings: {\n accessTokenPath: [\"body\", \"access_token\"],\n authorizationUrl: \"https://account.withings.com/oauth2_user/authorize2\",\n isOIDC: false,\n isRefreshable: true,\n profileRequest: {\n authIn: \"header\",\n body: async (config) => {\n const props = await getWithingsProps(config);\n if (props === undefined)\n throw new Error(\"Failed to get Withings search properties\");\n const { nonce, hashedSignature } = props;\n return [\n [\"action\", \"getuser\"],\n [\"nonce\", nonce],\n [\"client_id\", config.clientId],\n [\"signature\", hashedSignature]\n ];\n },\n encoding: \"application/x-www-form-urlencoded\",\n method: \"POST\",\n url: \"https://wbsapi.withings.net/v2/oauth2\"\n },\n refreshAccessTokenBody: {\n action: \"requesttoken\"\n },\n revocationRequest: {\n authIn: \"header\",\n body: async (config) => {\n const props = await getWithingsProps(config);\n if (!props)\n throw new Error(\"Failed to get Withings props\");\n const { nonce, hashedSignature } = props;\n return [\n [\"action\", \"revoke\"],\n [\"client_id\", config.clientId],\n [\"nonce\", nonce],\n [\"signature\", hashedSignature]\n ];\n },\n encoding: \"application/x-www-form-urlencoded\",\n method: \"POST\",\n url: \"https://wbsapi.withings.net/v2/oauth2\"\n },\n scopeDelimiter: \",\",\n scopeRequired: true,\n subject: [\"userid\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://wbsapi.withings.net/v2/oauth2\"\n },\n validateAuthorizationCodeBody: {\n action: \"requesttoken\"\n }\n },\n workos: {\n authorizationUrl: (config) => `https://${config.domain}/oauth2/authorize`,\n createAuthorizationURLSearchParams: () => {\n const nonce = crypto.randomUUID();\n return {\n nonce\n };\n },\n email: [\"email\"],\n familyName: [\"family_name\"],\n fullName: [\"name\"],\n givenName: [\"given_name\"],\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"POST\",\n url: (config) => `https://${config.domain}/oauth2/userinfo`\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://${config.domain}/oauth2/token`\n }\n },\n yahoo: {\n authorizationUrl: \"https://api.login.yahoo.com/oauth2/request_auth\",\n isOIDC: true,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.login.yahoo.com/openid/v1/userinfo\"\n },\n revocationRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n url: \"https://api.login.yahoo.com/oauth2/revoke\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://api.login.yahoo.com/oauth2/get_token\"\n }\n },\n yandex: {\n authorizationUrl: \"https://oauth.yandex.com/authorize\",\n createAuthorizationURLSearchParams: {\n device_id: crypto.randomUUID(),\n device_name: `${navigator.platform ?? \"Unknown\"} \\u2014 ${(navigator.userAgent.split(\")\")[0] || \"\").split(\"(\").pop() || \"Unknown\"}`\n },\n email: [\"default_email\"],\n familyName: [\"last_name\"],\n fullName: [\"real_name\"],\n givenName: [\"first_name\"],\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://login.yandex.ru/info\"\n },\n revocationRequest: {\n authIn: \"body\",\n encoding: \"application/json\",\n tokenParamName: \"access_token\",\n url: \"https://oauth.yandex.com/revoke_token\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://oauth.yandex.com/token\"\n }\n },\n zoho: {\n authorizationUrl: (config) => `https://accounts.zoho.${config.region ?? \"com\"}/oauth/v2/auth`,\n isOIDC: false,\n isRefreshable: true,\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: (config) => `https://accounts.zoho.${config.region ?? \"com\"}/oauth/user/info`\n },\n revocationRequest: {\n authIn: \"query\",\n encoding: \"application/x-www-form-urlencoded\",\n tokenParamName: \"token\",\n url: (config) => `https://accounts.zoho.${config.region ?? \"com\"}/oauth/v2/token/revoke`\n },\n scopeRequired: true,\n subject: [\"ZUID\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: (config) => `https://accounts.zoho.${config.region ?? \"com\"}/oauth/v2/token`\n }\n },\n zoom: {\n authorizationUrl: \"https://zoom.us/oauth/authorize\",\n email: [\"email\"],\n familyName: [\"last_name\"],\n givenName: [\"first_name\"],\n isOIDC: false,\n isRefreshable: true,\n picture: [\"pic_url\"],\n PKCEMethod: \"S256\",\n profileRequest: {\n authIn: \"header\",\n encoding: \"application/json\",\n method: \"GET\",\n url: \"https://api.zoom.us/v2/users/me\"\n },\n revocationRequest: {\n authIn: \"query\",\n encoding: \"application/json\",\n headers: (config) => ({\n Authorization: `Basic ${btoa(`${config.clientId}:${config.clientSecret}`)}`\n }),\n tokenParamName: \"token\",\n url: \"https://zoom.us/oauth/revoke\"\n },\n scopeRequired: false,\n subject: [\"id\"],\n subjectType: \"string\",\n tokenRequest: {\n authIn: \"body\",\n encoding: \"application/x-www-form-urlencoded\",\n url: \"https://zoom.us/oauth/token\"\n }\n }\n});\n\n// src/typeGuards.ts\nvar hasClientSecret = (credentials) => {\n if (typeof credentials !== \"object\" || credentials === null)\n return false;\n const secret = Reflect.get(credentials, \"clientSecret\");\n return typeof secret === \"string\";\n};\nvar isExpectedType = (value, kind) => {\n switch (kind) {\n case \"string\":\n return typeof value === \"string\";\n case \"number\":\n return typeof value === \"number\";\n case \"boolean\":\n return typeof value === \"boolean\";\n case \"object\":\n return typeof value === \"object\" && value !== null;\n default:\n return false;\n }\n};\nvar isObject = (value) => value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.prototype.toString.call(value) === \"[object Object]\";\nvar isOIDCProviderOption = (option) => {\n if (!isValidProviderOption(option))\n return false;\n const provider = providers[option];\n return provider.isOIDC;\n};\nvar isPKCEProviderOption = (option) => {\n if (!isValidProviderOption(option))\n return false;\n const provider = providers[option];\n return provider.PKCEMethod !== undefined;\n};\nvar isRefreshableOAuth2Client = (providerName, _client) => isRefreshableProviderOption(providerName);\nvar isRefreshableProviderOption = (option) => {\n if (!isValidProviderOption(option))\n return false;\n const provider = providers[option];\n return provider.isRefreshable;\n};\nvar isRevocableOAuth2Client = (providerName, _client) => isRevocableProviderOption(providerName);\nvar isRevocableProviderOption = (option) => {\n if (!isValidProviderOption(option))\n return false;\n const provider = providers[option];\n return provider.revocationRequest !== undefined;\n};\nvar isScopeRequiredProviderOption = (option) => {\n if (!isValidProviderOption(option))\n return false;\n const provider = providers[option];\n return provider.scopeRequired;\n};\nvar isValidProviderOption = (option) => Object.hasOwn(providers, option);\n\n// src/utils.ts\nvar createOAuth2FetchError = async (response) => {\n const clone = response.clone();\n const prefix = `HTTP ${response.status} ${response.statusText} for ${response.url}`;\n const payload = await response.json().catch(() => null);\n if (payload && typeof payload === \"object\" && Object.keys(payload).length) {\n return new Error(`${prefix}\n${JSON.stringify(payload)}`);\n }\n const text = await clone.text().catch(() => \"\");\n if (text) {\n return new Error(`${prefix}\n${text}`);\n }\n return new Error(prefix);\n};\nvar createOAuth2Request = ({\n url,\n body,\n authIn,\n headers,\n encoding,\n clientId,\n clientSecret\n}) => {\n const oauthHeaders = new Headers(headers);\n oauthHeaders.set(\"Accept\", \"application/json\");\n oauthHeaders.set(\"User-Agent\", \"citra\");\n if (authIn === \"header\") {\n if (!clientSecret) {\n throw new Error(\"clientSecret required for header auth\");\n }\n oauthHeaders.set(\"Authorization\", `Basic ${encodeBase64(`${clientId}:${clientSecret}`)}`);\n }\n if (encoding === \"application/json\") {\n oauthHeaders.set(\"Content-Type\", \"application/json\");\n return new Request(url, {\n body: JSON.stringify(body),\n headers: oauthHeaders,\n method: \"POST\"\n });\n }\n oauthHeaders.set(\"Content-Type\", \"application/x-www-form-urlencoded\");\n const entries = body instanceof URLSearchParams ? Array.from(body.entries()) : Object.entries(body).filter((entry) => typeof entry[1] === \"string\");\n const params = new URLSearchParams(entries);\n if (authIn === \"body\") {\n params.set(\"client_id\", clientId);\n clientSecret && params.set(\"client_secret\", clientSecret);\n }\n return new Request(url, {\n body: params.toString(),\n headers: oauthHeaders,\n method: \"POST\"\n });\n};\nvar decodeBase64 = (input, toUint8Array = false) => {\n const b64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"==\".slice(0, (BASE64_BLOCK_SIZE - input.length % BASE64_BLOCK_SIZE) % BASE64_BLOCK_SIZE);\n const raw = atob(b64);\n if (!toUint8Array) {\n return raw;\n }\n const bytes = new Uint8Array(raw.length);\n for (let i = 0;i < raw.length; i++) {\n bytes[i] = raw.charCodeAt(i);\n }\n return bytes;\n};\nvar decodeJWT = (tokenString) => {\n const [headerSegment, payloadSegment, signatureSegment] = tokenString.split(\".\");\n if (!headerSegment || !payloadSegment || !signatureSegment) {\n throw new Error(\"Invalid JWT format\");\n }\n const decodedPayload = decodeBase64(payloadSegment);\n if (typeof decodedPayload !== \"string\") {\n throw new Error(\"Expected JWT payload to be a UTF-8 string\");\n }\n const claims = JSON.parse(decodedPayload);\n return claims;\n};\nvar encodeBase64 = (input) => {\n let raw;\n if (typeof input === \"string\") {\n raw = input;\n } else {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n raw = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), \"\");\n }\n return btoa(raw);\n};\nvar getWithingsProps = async (config) => {\n const timestamp = Math.floor(Date.now() / 1000);\n const signature = `getnonce,${config.clientId},${timestamp}`;\n const hashedSignature = await hmacSha256(signature, config.clientSecret);\n const nonceUrl = new URL(\"https://wbsapi.withings.net/v2/signature\");\n nonceUrl.searchParams.set(\"action\", \"getnonce\");\n nonceUrl.searchParams.set(\"client_id\", config.clientId);\n nonceUrl.searchParams.set(\"timestamp\", timestamp.toString());\n nonceUrl.searchParams.set(\"signature\", hashedSignature);\n const nonceTarget = nonceUrl.toString();\n const nonceResponse = await fetch(nonceTarget, { method: \"POST\" });\n const nonceData = await nonceResponse.json();\n if (nonceData.status === 0) {\n return {\n hashedSignature,\n nonce: nonceData.body.nonce\n };\n }\n return;\n};\nvar hmacSha256 = async (message, secret) => {\n const encoder = new TextEncoder;\n const key = await crypto.subtle.importKey(\"raw\", encoder.encode(secret), { hash: \"SHA-256\", name: \"HMAC\" }, false, [\"sign\"]);\n const sigBuffer = await crypto.subtle.sign(\"HMAC\", key, encoder.encode(message));\n return Array.from(new Uint8Array(sigBuffer)).map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n};\nvar extractPropFromIdentity = (identity, keys, propType) => {\n let value = identity;\n for (const key of keys) {\n if (Array.isArray(value))\n value = value[Number(key)];\n if (!isObject(value)) {\n throw new Error(`Invalid identity data shape: expected object, got ${typeof value}`);\n }\n value = value[key];\n }\n if (propType !== undefined && !isExpectedType(value, propType)) {\n throw new Error(`Invalid identity data shape: expected ${propType}, got ${typeof value}`);\n }\n return value;\n};\nvar getProviderSubjectKeys = (providerConfiguration, source) => providerConfiguration.subjectBySource?.[source] ?? providerConfiguration.subject;\nvar setPropInIdentity = (identity, keys, value) => {\n if (keys.length === 0) {\n return identity;\n }\n let cursor = identity;\n for (const key of keys.slice(0, -1)) {\n const next = cursor[key];\n cursor[key] = isObject(next) ? next : {};\n cursor = cursor[key];\n }\n cursor[keys[keys.length - 1]] = value;\n return identity;\n};\nvar normalizeProviderIdentity = ({\n identity,\n providerConfiguration,\n source\n}) => {\n const sourceKeys = getProviderSubjectKeys(providerConfiguration, source);\n const canonicalKeys = providerConfiguration.subject;\n if (sourceKeys.join(\".\") === canonicalKeys.join(\".\")) {\n return identity;\n }\n const subject = extractPropFromIdentity(identity, sourceKeys, providerConfiguration.subjectType);\n const normalizedIdentity = structuredClone(identity);\n return setPropInIdentity(normalizedIdentity, canonicalKeys, subject);\n};\n\n// src/arctic-utils.ts\nvar createS256CodeChallenge = async (codeVerifier) => {\n const data = new TextEncoder().encode(codeVerifier);\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", data);\n return base64Url(hashBuffer);\n};\nvar createRandomBase64UrlGenerator = (length) => () => {\n const buffer = crypto.getRandomValues(new Uint8Array(length));\n return base64Url(buffer);\n};\nvar generateCodeVerifier = createRandomBase64UrlGenerator(NUM_GENERATOR_BYTES);\nvar generateState = createRandomBase64UrlGenerator(NUM_GENERATOR_BYTES);\nvar base64Url = (input) => encodeBase64(input).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n// src/oidc.ts\nvar ALG_ES256 = \"ES256\";\nvar ALG_RS256 = \"RS256\";\nvar CLOCK_SKEW_SECONDS = 60;\nvar DEFAULT_SCOPES = [\"openid\", \"email\", \"profile\"];\nvar JWKS_REFETCH_COOLDOWN_MS = 60000;\nvar JWT_SEGMENT_COUNT = 3;\nvar MILLISECONDS_PER_SECOND = 1000;\nvar trimTrailingSlash = (value) => value.endsWith(\"/\") ? value.slice(0, -1) : value;\nvar fetchJson = async (url) => {\n const response = await fetch(url, {\n headers: { accept: \"application/json\" }\n });\n if (!response.ok) {\n throw new Error(`Request to ${url} failed with status ${response.status}`);\n }\n return response.json();\n};\nvar decodeJsonSegment = (segment) => {\n const decoded = decodeBase64(segment);\n if (typeof decoded !== \"string\") {\n throw new Error(\"Expected a base64url-encoded JWT segment\");\n }\n const parsed = JSON.parse(decoded);\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(\"Expected a JWT segment to decode to a JSON object\");\n }\n return parsed;\n};\nvar importVerificationKey = (jwk, alg) => {\n if (alg === ALG_RS256) {\n return crypto.subtle.importKey(\"jwk\", jwk, { hash: \"SHA-256\", name: \"RSASSA-PKCS1-v1_5\" }, false, [\"verify\"]);\n }\n if (alg === ALG_ES256) {\n return crypto.subtle.importKey(\"jwk\", jwk, { name: \"ECDSA\", namedCurve: \"P-256\" }, false, [\"verify\"]);\n }\n throw new Error(`Unsupported id_token signing algorithm \"${alg}\"`);\n};\nvar verifySignature = (key, alg, signingInput, signature) => {\n const algorithm = alg === ALG_ES256 ? { hash: \"SHA-256\", name: \"ECDSA\" } : { name: \"RSASSA-PKCS1-v1_5\" };\n return crypto.subtle.verify(algorithm, key, new Uint8Array(signature), new TextEncoder().encode(signingInput));\n};\nvar selectKey = (jwks, kid) => jwks.find((jwk) => kid === undefined || jwk.kid === kid);\nvar assertClaims = (payload, expected) => {\n const aud = Reflect.get(payload, \"aud\");\n const exp = Reflect.get(payload, \"exp\");\n const iss = Reflect.get(payload, \"iss\");\n const sub = Reflect.get(payload, \"sub\");\n const audiences = Array.isArray(aud) ? aud : [aud];\n const nowSeconds = Math.floor(Date.now() / MILLISECONDS_PER_SECOND);\n if (iss !== expected.issuer) {\n throw new Error('id_token \"iss\" does not match the provider issuer');\n }\n if (typeof sub !== \"string\" || sub.length === 0) {\n throw new Error('id_token is missing \"sub\"');\n }\n if (!audiences.includes(expected.audience)) {\n throw new Error('id_token \"aud\" does not include the client id');\n }\n if (typeof exp !== \"number\" || exp + CLOCK_SKEW_SECONDS < nowSeconds) {\n throw new Error(\"id_token has expired\");\n }\n if (expected.nonce !== undefined && Reflect.get(payload, \"nonce\") !== expected.nonce) {\n throw new Error('id_token \"nonce\" does not match');\n }\n const claims = { ...payload, aud, exp, iss, sub };\n return claims;\n};\nvar verifyIdToken = async ({\n audience,\n idToken,\n issuer,\n jwks,\n nonce\n}) => {\n const segments = idToken.split(\".\");\n const [headerSegment, payloadSegment, signatureSegment] = segments;\n if (segments.length !== JWT_SEGMENT_COUNT || headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {\n throw new Error(\"Invalid id_token: expected three segments\");\n }\n const header = decodeJsonSegment(headerSegment);\n const alg = Reflect.get(header, \"alg\");\n const kid = Reflect.get(header, \"kid\");\n if (typeof alg !== \"string\") {\n throw new Error('id_token header is missing \"alg\"');\n }\n const jwk = selectKey(jwks, typeof kid === \"string\" ? kid : undefined);\n if (jwk === undefined) {\n throw new Error('No JWKS key matches the id_token \"kid\"');\n }\n const key = await importVerificationKey(jwk, alg);\n const signature = decodeBase64(signatureSegment, true);\n if (typeof signature === \"string\") {\n throw new Error(\"Failed to decode the id_token signature\");\n }\n const isValid = await verifySignature(key, alg, `${headerSegment}.${payloadSegment}`, signature);\n if (!isValid) {\n throw new Error(\"id_token signature verification failed\");\n }\n return assertClaims(decodeJsonSegment(payloadSegment), {\n audience,\n issuer,\n nonce\n });\n};\nvar toDiscoveryDocument = (value, expectedIssuer) => {\n if (typeof value !== \"object\" || value === null) {\n throw new Error(\"OIDC discovery document is not a JSON object\");\n }\n const issuer = Reflect.get(value, \"issuer\");\n const authorizationEndpoint = Reflect.get(value, \"authorization_endpoint\");\n const tokenEndpoint = Reflect.get(value, \"token_endpoint\");\n const jwksUri = Reflect.get(value, \"jwks_uri\");\n const userinfoEndpoint = Reflect.get(value, \"userinfo_endpoint\");\n if (typeof issuer !== \"string\" || typeof authorizationEndpoint !== \"string\" || typeof tokenEndpoint !== \"string\" || typeof jwksUri !== \"string\") {\n throw new Error(\"OIDC discovery document is missing required endpoints\");\n }\n if (trimTrailingSlash(issuer) !== trimTrailingSlash(expectedIssuer)) {\n throw new Error(`OIDC discovery issuer \"${issuer}\" does not match configured issuer \"${expectedIssuer}\"`);\n }\n return {\n authorization_endpoint: authorizationEndpoint,\n issuer,\n jwks_uri: jwksUri,\n token_endpoint: tokenEndpoint,\n userinfo_endpoint: typeof userinfoEndpoint === \"string\" ? userinfoEndpoint : undefined\n };\n};\nvar fetchDiscoveryDocument = async (issuer) => {\n const base = trimTrailingSlash(issuer);\n const document = await fetchJson(`${base}/.well-known/openid-configuration`);\n return toDiscoveryDocument(document, issuer);\n};\nvar fetchJwks = async (jwksUri) => {\n const body = await fetchJson(jwksUri);\n const value = Reflect.get(body ?? {}, \"keys\");\n if (!Array.isArray(value)) {\n throw new Error('JWKS response is missing a \"keys\" array');\n }\n const keys = value;\n return keys;\n};\nvar createOIDCClient = async (config) => {\n const discovery = await fetchDiscoveryDocument(config.issuer);\n const scopes = config.scopes !== undefined && config.scopes.length > 0 ? config.scopes : DEFAULT_SCOPES;\n let cachedJwks;\n let jwksFetchedAtMs = 0;\n const resolveJwks = async (forceRefresh) => {\n const isStale = Date.now() - jwksFetchedAtMs > JWKS_REFETCH_COOLDOWN_MS;\n if (cachedJwks === undefined || forceRefresh && isStale) {\n cachedJwks = await fetchJwks(discovery.jwks_uri);\n jwksFetchedAtMs = Date.now();\n }\n return cachedJwks;\n };\n const createAuthorizationUrl = async (options) => {\n const url = new URL(discovery.authorization_endpoint);\n const challenge = await createS256CodeChallenge(options.codeVerifier);\n const { searchParams } = url;\n searchParams.set(\"client_id\", config.clientId);\n searchParams.set(\"code_challenge\", challenge);\n searchParams.set(\"code_challenge_method\", \"S256\");\n searchParams.set(\"redirect_uri\", config.redirectUri);\n searchParams.set(\"response_type\", \"code\");\n searchParams.set(\"scope\", (options.scope ?? scopes).join(\" \"));\n searchParams.set(\"state\", options.state);\n if (options.nonce !== undefined) {\n searchParams.set(\"nonce\", options.nonce);\n }\n return url;\n };\n const validateAuthorizationCode = async (options) => {\n const body = new URLSearchParams;\n body.set(\"client_id\", config.clientId);\n body.set(\"client_secret\", config.clientSecret);\n body.set(\"code\", options.code);\n body.set(\"code_verifier\", options.codeVerifier);\n body.set(\"grant_type\", \"authorization_code\");\n body.set(\"redirect_uri\", config.redirectUri);\n const response = await fetch(discovery.token_endpoint, {\n body,\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/x-www-form-urlencoded\"\n },\n method: \"POST\"\n });\n if (!response.ok) {\n throw new Error(`OIDC token request failed with status ${response.status}`);\n }\n const tokens = await response.json();\n return tokens;\n };\n const verifyToken = async (idToken, options) => {\n const jwks = await resolveJwks(false);\n const params = {\n audience: config.clientId,\n idToken,\n issuer: discovery.issuer,\n nonce: options?.nonce\n };\n try {\n return await verifyIdToken({ ...params, jwks });\n } catch (error) {\n const refreshed = await resolveJwks(true);\n if (refreshed === jwks)\n throw error;\n return verifyIdToken({ ...params, jwks: refreshed });\n }\n };\n const fetchUserProfile = async (accessToken) => {\n if (discovery.userinfo_endpoint === undefined) {\n throw new Error(\"OIDC provider does not expose a userinfo endpoint\");\n }\n const response = await fetch(discovery.userinfo_endpoint, {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${accessToken}`\n }\n });\n if (!response.ok) {\n throw new Error(`OIDC userinfo request failed with status ${response.status}`);\n }\n const profile = await response.json();\n return profile;\n };\n return {\n createAuthorizationUrl,\n discovery,\n fetchUserProfile,\n validateAuthorizationCode,\n verifyIdToken: verifyToken\n };\n};\n// src/providerOptions.ts\nvar oidcProviderOptions = Object.keys(providers).filter(isOIDCProviderOption);\nvar pkceProviderOptions = Object.keys(providers).filter(isPKCEProviderOption);\nvar providerOptions = Object.keys(providers).filter(isValidProviderOption);\nvar refreshableProviderOptions = Object.keys(providers).filter(isRefreshableProviderOption);\nvar revocableProviderOptions = Object.keys(providers).filter(isRevocableProviderOption);\nvar scopeRequiredProviderOptions = Object.keys(providers).filter(isScopeRequiredProviderOption);\n\n// src/index.ts\nvar readPath = (value, path) => path.reduce((cursor, key) => cursor && typeof cursor === \"object\" ? Reflect.get(cursor, key) : undefined, value);\nvar buildOAuth2Client = async (meta, config) => {\n const isConfigPropertyFunction = (cfgProp) => typeof cfgProp === \"function\";\n const resolveConfigProp = async (cfgProp) => {\n const result = isConfigPropertyFunction(cfgProp) ? cfgProp(config) : cfgProp;\n return result;\n };\n const authorizationUrl = await resolveConfigProp(meta.authorizationUrl);\n const tokenUrl = await resolveConfigProp(meta.tokenRequest.url);\n return {\n async createAuthorizationUrl(opts) {\n const { state, scope = [], searchParams = [], codeVerifier } = opts;\n const url = new URL(authorizationUrl);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", config.clientId);\n if (config.redirectUri)\n url.searchParams.set(\"redirect_uri\", config.redirectUri);\n if (state)\n url.searchParams.set(\"state\", state);\n if (scope.length !== 0) {\n url.searchParams.set(meta.scopeParamName ?? \"scope\", scope.join(meta.scopeDelimiter ?? \" \"));\n }\n if (meta.PKCEMethod !== undefined) {\n if (!codeVerifier) {\n throw new Error(\"`codeVerifier` is required when PKCE is enabled\");\n }\n const codeChallenge = meta.PKCEMethod === \"S256\" ? await createS256CodeChallenge(codeVerifier) : codeVerifier;\n url.searchParams.set(\"code_challenge_method\", meta.PKCEMethod);\n url.searchParams.set(\"code_challenge\", codeChallenge);\n }\n Object.entries(resolveConfigProp(meta.createAuthorizationURLSearchParams) ?? {}).forEach(([key, value]) => url.searchParams.set(key, value));\n searchParams.forEach(([key, value]) => url.searchParams.set(key, value));\n return url;\n },\n async fetchUserProfile(accessToken) {\n const { profileRequest } = meta;\n if (!profileRequest) {\n throw new Error(\"OIDC provider exposes identity through the id_token and does not define a UserInfo endpoint\");\n }\n const {\n url,\n method,\n authIn,\n searchParams,\n body: profileBody,\n headers,\n encoding\n } = profileRequest;\n const endpoint = new URL(await resolveConfigProp(url));\n const resolvedBody = await resolveConfigProp(profileBody);\n new URLSearchParams(await resolveConfigProp(searchParams)).forEach((value, key) => endpoint.searchParams.append(key, value));\n let headerEntries = [];\n const rawHeaders = headers ? await resolveConfigProp(headers) : undefined;\n if (rawHeaders instanceof Headers)\n headerEntries = Array.from(rawHeaders.entries());\n else if (Array.isArray(rawHeaders))\n headerEntries = rawHeaders;\n else if (rawHeaders && typeof rawHeaders === \"object\")\n headerEntries = Object.entries(rawHeaders);\n const profileHeaders = Object.fromEntries(headerEntries.filter(([, value]) => value !== \"\"));\n if (authIn === \"header\") {\n profileHeaders.Authorization = `Bearer ${accessToken}`;\n } else if (authIn === \"path\") {\n endpoint.pathname = `${endpoint.pathname.replace(/\\/+$/, \"\")}/${accessToken}`;\n } else {\n endpoint.searchParams.append(\"access_token\", accessToken);\n }\n const init = { headers: profileHeaders, method };\n if (method === \"POST\" && resolvedBody !== undefined) {\n profileHeaders[\"Content-Type\"] = encoding;\n init.body = encoding === \"application/json\" ? JSON.stringify(resolvedBody) : new URLSearchParams(resolvedBody).toString();\n }\n const profileTarget = endpoint.toString();\n const response = await fetch(profileTarget, init);\n if (!response.ok)\n throw await createOAuth2FetchError(response);\n return response.json();\n },\n async refreshAccessToken(refreshToken) {\n const { authIn, encoding } = meta.tokenRequest;\n const params = new URLSearchParams(meta.refreshAccessTokenBody);\n params.set(\"grant_type\", \"refresh_token\");\n params.set(\"refresh_token\", refreshToken);\n const { clientId } = config;\n let clientSecretValue;\n if (hasClientSecret(config)) {\n clientSecretValue = config.clientSecret;\n params.set(\"client_id\", clientId);\n params.set(\"client_secret\", clientSecretValue);\n }\n const request = createOAuth2Request({\n authIn,\n body: params,\n clientId,\n clientSecret: clientSecretValue,\n encoding,\n url: tokenUrl\n });\n const response = await fetch(request);\n if (!response.ok)\n throw await createOAuth2FetchError(response);\n return response.json();\n },\n async revokeToken(token) {\n const { revocationRequest } = meta;\n if (!revocationRequest) {\n throw new Error(\"Token revocation not defined for this provider\");\n }\n const { url, authIn, body, headers, tokenParamName } = revocationRequest;\n const endpoint = await resolveConfigProp(url);\n const resolvedBody = await resolveConfigProp(body);\n const revocationBody = new URLSearchParams(resolvedBody);\n const revocationHeaders = new Headers(headers && await resolveConfigProp(headers));\n const { clientId } = config;\n const clientSecret = hasClientSecret(config) ? config.clientSecret : undefined;\n let request;\n if (authIn === \"body\") {\n revocationBody.set(tokenParamName, token);\n revocationBody.set(\"client_id\", clientId);\n if (clientSecret)\n revocationBody.set(\"client_secret\", clientSecret);\n request = createOAuth2Request({\n authIn: \"body\",\n body: revocationBody,\n clientId,\n clientSecret,\n encoding: \"application/x-www-form-urlencoded\",\n headers: revocationHeaders,\n url: endpoint.toString()\n });\n } else if (authIn === \"header\") {\n revocationHeaders.set(\"Authorization\", `Bearer ${token}`);\n request = createOAuth2Request({\n authIn: \"header\",\n body: revocationBody,\n clientId,\n clientSecret,\n encoding: \"application/x-www-form-urlencoded\",\n headers: revocationHeaders,\n url: endpoint.toString()\n });\n } else {\n request = createOAuth2Request({\n authIn: \"query\",\n body: revocationBody,\n clientId,\n clientSecret,\n encoding: \"application/x-www-form-urlencoded\",\n headers: revocationHeaders,\n url: `${endpoint.toString()}?${tokenParamName}=${token}`\n });\n }\n const response = await fetch(request);\n if (!response.ok)\n throw await createOAuth2FetchError(response);\n },\n async validateAuthorizationCode(opts) {\n const { code, codeVerifier } = opts;\n const { authIn, encoding } = meta.tokenRequest;\n const bodyObj = {};\n for (const key in meta.validateAuthorizationCodeBody ?? {}) {\n const value = meta.validateAuthorizationCodeBody[key];\n if (typeof value === \"string\")\n bodyObj[key] = value;\n }\n bodyObj.grant_type = \"authorization_code\";\n bodyObj.code = code;\n if (config.redirectUri)\n bodyObj.redirect_uri = config.redirectUri;\n if (meta.PKCEMethod !== undefined) {\n if (!codeVerifier) {\n throw new Error(\"codeVerifier required when PKCE is enabled\");\n }\n bodyObj.code_verifier = codeVerifier;\n }\n const payload = encoding === \"application/json\" ? bodyObj : new URLSearchParams(bodyObj);\n const request = createOAuth2Request({\n authIn,\n body: payload,\n clientId: config.clientId,\n clientSecret: hasClientSecret(config) ? config.clientSecret : undefined,\n encoding,\n url: tokenUrl\n });\n const response = await fetch(request);\n if (!response.ok)\n throw await createOAuth2FetchError(response);\n const tokenResponse = await response.json();\n if (!tokenResponse || typeof tokenResponse !== \"object\") {\n throw new Error(\"OAuth token endpoint returned a non-object response\");\n }\n const oauthError = Reflect.get(tokenResponse, \"error\");\n if (typeof oauthError === \"string\" && oauthError.length > 0) {\n throw new Error(`OAuth token exchange failed: ${oauthError}`);\n }\n const nestedToken = meta.accessTokenPath ? readPath(tokenResponse, meta.accessTokenPath) : undefined;\n if (typeof nestedToken === \"string\" && nestedToken.length > 0 && tokenResponse && typeof tokenResponse === \"object\") {\n tokenResponse.access_token = nestedToken;\n }\n const accessToken = Reflect.get(tokenResponse, \"access_token\");\n if (typeof accessToken !== \"string\" || accessToken.length === 0) {\n throw new Error(\"OAuth token endpoint returned no access_token\");\n }\n return tokenResponse;\n }\n };\n};\nvar createCustomOAuth2Client = (providerConfig, credentials) => buildOAuth2Client(providerConfig, credentials);\nvar createOAuth2Client = (providerName, config) => buildOAuth2Client(providers[providerName], config);\nexport {\n verifyIdToken,\n scopeRequiredProviderOptions,\n revocableProviderOptions,\n refreshableProviderOptions,\n providers,\n providerOptions,\n pkceProviderOptions,\n oidcProviderOptions,\n normalizeProviderIdentity,\n isValidProviderOption,\n isScopeRequiredProviderOption,\n isRevocableProviderOption,\n isRevocableOAuth2Client,\n isRefreshableProviderOption,\n isRefreshableOAuth2Client,\n isPKCEProviderOption,\n isObject,\n isOIDCProviderOption,\n isExpectedType,\n hmacSha256,\n hasClientSecret,\n getWithingsProps,\n getProviderSubjectKeys,\n generateState,\n generateCodeVerifier,\n extractPropFromIdentity,\n encodeBase64,\n defineProviders,\n defineProvider,\n decodeJWT,\n decodeBase64,\n createS256CodeChallenge,\n createOIDCClient,\n createOAuth2Request,\n createOAuth2FetchError,\n createOAuth2Client,\n createCustomOAuth2Client\n};\n",
|
|
20
20
|
"import { eq } from 'drizzle-orm';\nimport { bigint, integer, pgTable, varchar } from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type { LockoutRecord, LockoutStore } from './types';\n\nconst KEY_LENGTH = 320;\n\nexport const lockoutsTable = pgTable('auth_lockouts', {\n\tfailed_attempts: integer('failed_attempts').notNull().default(0),\n\tkey: varchar('key', { length: KEY_LENGTH }).primaryKey(),\n\tlocked_until_ms: bigint('locked_until_ms', { mode: 'number' }),\n\twindow_started_at_ms: bigint('window_started_at_ms', {\n\t\tmode: 'number'\n\t}).notNull()\n});\n\ntype LockoutRow = typeof lockoutsTable.$inferSelect;\ntype LockoutInsert = typeof lockoutsTable.$inferInsert;\n\nconst toRecord = (row: LockoutRow): LockoutRecord => ({\n\tfailedAttempts: row.failed_attempts,\n\tkey: row.key,\n\tlockedUntil: row.locked_until_ms ?? undefined,\n\twindowStartedAt: row.window_started_at_ms\n});\n\nexport const createNeonLockoutStore = (databaseUrl: string) =>\n\tcreatePostgresLockoutStore(createNeonDatabase(databaseUrl));\nexport const createPostgresLockoutStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): LockoutStore => {\n\tconst get = async (key: string) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(lockoutsTable)\n\t\t\t.where(eq(lockoutsTable.key, key))\n\t\t\t.limit(1);\n\n\t\treturn row ? toRecord(row) : undefined;\n\t};\n\tconst save = async (record: LockoutRecord) => {\n\t\tconst values: LockoutInsert = {\n\t\t\tfailed_attempts: record.failedAttempts,\n\t\t\tkey: record.key,\n\t\t\tlocked_until_ms: record.lockedUntil ?? null,\n\t\t\twindow_started_at_ms: record.windowStartedAt\n\t\t};\n\t\tawait db\n\t\t\t.insert(lockoutsTable)\n\t\t\t.values(values)\n\t\t\t.onConflictDoUpdate({ set: values, target: lockoutsTable.key });\n\t};\n\n\treturn {\n\t\tget,\n\t\tincrement: async (key, windowMs) => {\n\t\t\tconst now = Date.now();\n\t\t\tconst existing = await get(key);\n\t\t\tconst next: LockoutRecord =\n\t\t\t\texisting !== undefined &&\n\t\t\t\tnow - existing.windowStartedAt <= windowMs\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...existing,\n\t\t\t\t\t\t\tfailedAttempts: existing.failedAttempts + 1\n\t\t\t\t\t\t}\n\t\t\t\t\t: { failedAttempts: 1, key, windowStartedAt: now };\n\t\t\tawait save(next);\n\n\t\t\treturn next;\n\t\t},\n\t\tlock: async (key, lockedUntil) => {\n\t\t\tconst existing = (await get(key)) ?? {\n\t\t\t\tfailedAttempts: 0,\n\t\t\t\tkey,\n\t\t\t\twindowStartedAt: Date.now()\n\t\t\t};\n\t\t\tawait save({ ...existing, lockedUntil });\n\t\t},\n\t\treset: async (key) => {\n\t\t\tawait db.delete(lockoutsTable).where(eq(lockoutsTable.key, key));\n\t\t}\n\t};\n};\n",
|
|
21
21
|
"import { eq } from 'drizzle-orm';\nimport {\n\tbigint,\n\tboolean,\n\tjsonb,\n\tpgTable,\n\tsmallint,\n\ttext,\n\tvarchar\n} from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type { MfaEnrollment, MFAStore } from './types';\n\nconst ID_LENGTH = 255;\nconst PHONE_LENGTH = 20;\n\nexport const mfaEnrollmentsTable = pgTable('auth_mfa_enrollments', {\n\tbackup_code_hashes: jsonb('backup_code_hashes')\n\t\t.$type<string[]>()\n\t\t.notNull()\n\t\t.default([]),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tlast_used_at_ms: bigint('last_used_at_ms', { mode: 'number' }),\n\tsms_failed_attempts: smallint('sms_failed_attempts').notNull().default(0),\n\tsms_pending_code_expires_at_ms: bigint('sms_pending_code_expires_at_ms', {\n\t\tmode: 'number'\n\t}),\n\tsms_pending_code_hash: text('sms_pending_code_hash'),\n\tsms_phone: varchar('sms_phone', { length: PHONE_LENGTH }),\n\tsms_verified: boolean('sms_verified').notNull().default(false),\n\ttotp_failed_attempts: smallint('totp_failed_attempts').notNull().default(0),\n\ttotp_secret_ciphertext: text('totp_secret_ciphertext'),\n\ttotp_verified: boolean('totp_verified').notNull().default(false),\n\tupdated_at_ms: bigint('updated_at_ms', { mode: 'number' }).notNull(),\n\tuser_id: varchar('user_id', { length: ID_LENGTH }).primaryKey()\n});\n\ntype MfaRow = typeof mfaEnrollmentsTable.$inferSelect;\ntype MfaInsert = typeof mfaEnrollmentsTable.$inferInsert;\n\nconst toEnrollment = (row: MfaRow): MfaEnrollment => ({\n\tbackupCodeHashes: row.backup_code_hashes,\n\tcreatedAt: row.created_at_ms,\n\tlastUsedAt: row.last_used_at_ms ?? undefined,\n\tsmsFailedAttempts: row.sms_failed_attempts,\n\tsmsPendingCodeExpiresAt: row.sms_pending_code_expires_at_ms ?? undefined,\n\tsmsPendingCodeHash: row.sms_pending_code_hash ?? undefined,\n\tsmsPhone: row.sms_phone ?? undefined,\n\tsmsVerified: row.sms_verified,\n\ttotpFailedAttempts: row.totp_failed_attempts,\n\ttotpSecretCiphertext: row.totp_secret_ciphertext ?? undefined,\n\ttotpVerified: row.totp_verified,\n\tupdatedAt: row.updated_at_ms,\n\tuserId: row.user_id\n});\n\nexport const createNeonMfaStore = (databaseUrl: string) =>\n\tcreatePostgresMfaStore(createNeonDatabase(databaseUrl));\nexport const createPostgresMfaStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): MFAStore => ({\n\tgetEnrollment: async (userId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(mfaEnrollmentsTable)\n\t\t\t.where(eq(mfaEnrollmentsTable.user_id, userId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toEnrollment(row) : undefined;\n\t},\n\tlistEnrollments: async () => {\n\t\tconst rows = await db.select().from(mfaEnrollmentsTable);\n\n\t\treturn rows.map(toEnrollment);\n\t},\n\tremoveEnrollment: async (userId) => {\n\t\tawait db\n\t\t\t.delete(mfaEnrollmentsTable)\n\t\t\t.where(eq(mfaEnrollmentsTable.user_id, userId));\n\t},\n\tsaveEnrollment: async (enrollment) => {\n\t\tconst values: MfaInsert = {\n\t\t\tbackup_code_hashes: enrollment.backupCodeHashes,\n\t\t\tcreated_at_ms: enrollment.createdAt,\n\t\t\tlast_used_at_ms: enrollment.lastUsedAt ?? null,\n\t\t\tsms_failed_attempts: enrollment.smsFailedAttempts ?? 0,\n\t\t\tsms_pending_code_expires_at_ms:\n\t\t\t\tenrollment.smsPendingCodeExpiresAt ?? null,\n\t\t\tsms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,\n\t\t\tsms_phone: enrollment.smsPhone ?? null,\n\t\t\tsms_verified: enrollment.smsVerified,\n\t\t\ttotp_failed_attempts: enrollment.totpFailedAttempts ?? 0,\n\t\t\ttotp_secret_ciphertext: enrollment.totpSecretCiphertext ?? null,\n\t\t\ttotp_verified: enrollment.totpVerified,\n\t\t\tupdated_at_ms: enrollment.updatedAt,\n\t\t\tuser_id: enrollment.userId\n\t\t};\n\t\tawait db.insert(mfaEnrollmentsTable).values(values).onConflictDoUpdate({\n\t\t\tset: values,\n\t\t\ttarget: mfaEnrollmentsTable.user_id\n\t\t});\n\t}\n});\n",
|
|
22
|
-
"import { and, desc, eq, gt, lt } from 'drizzle-orm';\nimport {\n\tbigint,\n\tboolean,\n\tjsonb,\n\tpgTable,\n\ttext,\n\tvarchar\n} from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type {\n\tAuthorizationCode,\n\tAuthorizationCodeStore,\n\tBackchannelAuthRequest,\n\tBackchannelAuthStatus,\n\tBackchannelAuthStore,\n\tClientAssertionJtiStore,\n\tClientRegistrationToken,\n\tClientRegistrationTokenStore,\n\tDeviceAuthorization,\n\tDeviceAuthorizationStatus,\n\tDeviceAuthorizationStore,\n\tInitialAccessTokenStore,\n\tLogoutDelivery,\n\tLogoutDeliveryStore,\n\tOAuthClient,\n\tOAuthClientStore,\n\tOidcRefreshToken,\n\tOidcRefreshTokenStore,\n\tPushedAuthorizationRequest,\n\tPushedAuthorizationRequestStore\n} from './types';\n\nconst URL_LENGTH = 2048;\nconst DEFAULT_LIST_LIMIT = 100;\n\nconst ID_LENGTH = 255;\nconst BACKCHANNEL_AUTH_STATUSES = new Set<BackchannelAuthStatus>([\n\t'approved',\n\t'denied',\n\t'pending'\n]);\nconst DEVICE_AUTHORIZATION_STATUSES = new Set<DeviceAuthorizationStatus>([\n\t'approved',\n\t'denied',\n\t'pending'\n]);\n\nconst backchannelAuthStatus = (value: string) => {\n\tfor (const status of BACKCHANNEL_AUTH_STATUSES)\n\t\tif (status === value) return status;\n\n\tthrow new Error(\n\t\t`Invalid persisted backchannel authorization status: ${value}`\n\t);\n};\n\nconst deviceAuthorizationStatus = (value: string) => {\n\tfor (const status of DEVICE_AUTHORIZATION_STATUSES)\n\t\tif (status === value) return status;\n\n\tthrow new Error(`Invalid persisted device authorization status: ${value}`);\n};\n\nexport const oauthBackchannelAuthRequestsTable = pgTable(\n\t'auth_oauth_backchannel_auth_requests',\n\t{\n\t\tauth_req_id: varchar('auth_req_id', { length: ID_LENGTH }).primaryKey(),\n\t\tbinding_message: text('binding_message'),\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tinterval_seconds: bigint('interval_seconds', {\n\t\t\tmode: 'number'\n\t\t}).notNull(),\n\t\tlast_polled_at_ms: bigint('last_polled_at_ms', { mode: 'number' }),\n\t\tscopes: text('scopes').array().notNull(),\n\t\tstatus: varchar('status', { length: 16 }).notNull(),\n\t\tuser_sub: varchar('user_sub', { length: ID_LENGTH })\n\t}\n);\nexport const oauthClientAssertionJtisTable = pgTable(\n\t'auth_oauth_client_assertion_jtis',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcomposite_key: varchar('composite_key', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tjti: varchar('jti', { length: ID_LENGTH }).notNull()\n\t}\n);\nexport const oauthClientRegistrationTokensTable = pgTable(\n\t'auth_oauth_client_registration_tokens',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey()\n\t}\n);\nexport const oauthClientsTable = pgTable('auth_oauth_clients', {\n\tbackchannel_logout_uri: varchar('backchannel_logout_uri', {\n\t\tlength: URL_LENGTH\n\t}),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).primaryKey(),\n\thashed_secret: varchar('hashed_secret', { length: ID_LENGTH }),\n\tjwks_json: jsonb('jwks_json').$type<JsonWebKey[]>(),\n\tjwks_uri: varchar('jwks_uri', { length: URL_LENGTH }),\n\tname: varchar('name', { length: ID_LENGTH }).notNull(),\n\tpost_logout_redirect_uris: text('post_logout_redirect_uris').array(),\n\tredirect_uris: text('redirect_uris').array().notNull(),\n\trequire_pushed_authorization_requests: boolean(\n\t\t'require_pushed_authorization_requests'\n\t),\n\trequire_signed_request_object: boolean('require_signed_request_object'),\n\tscopes: text('scopes').array().notNull()\n});\nexport const oauthCodesTable = pgTable('auth_oauth_codes', {\n\tacr: varchar('acr', { length: ID_LENGTH }),\n\taudience: varchar('audience', { length: URL_LENGTH }),\n\tclaims_json: jsonb('claims_json').$type<Record<string, unknown>>(),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\tcode_challenge: varchar('code_challenge', { length: ID_LENGTH }).notNull(),\n\tcode_hash: varchar('code_hash', { length: ID_LENGTH }).primaryKey(),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tdpop_jkt: varchar('dpop_jkt', { length: ID_LENGTH }),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\tnonce: varchar('nonce', { length: ID_LENGTH }),\n\tredirect_uri: varchar('redirect_uri', { length: ID_LENGTH }).notNull(),\n\tscopes: text('scopes').array().notNull(),\n\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n});\nexport const oauthDeviceAuthorizationsTable = pgTable(\n\t'auth_oauth_device_authorizations',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\tdevice_code_hash: varchar('device_code_hash', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tinterval_seconds: bigint('interval_seconds', {\n\t\t\tmode: 'number'\n\t\t}).notNull(),\n\t\tscopes: text('scopes').array().notNull(),\n\t\tstatus: varchar('status', { length: 16 }).notNull(),\n\t\tuser_code: varchar('user_code', { length: 16 }).notNull().unique(),\n\t\tuser_sub: varchar('user_sub', { length: ID_LENGTH })\n\t}\n);\nexport const oauthInitialAccessTokensTable = pgTable(\n\t'auth_oauth_initial_access_tokens',\n\t{\n\t\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey()\n\t}\n);\nexport const oauthLogoutDeliveriesTable = pgTable(\n\t'auth_oauth_logout_deliveries',\n\t{\n\t\tattempts: bigint('attempts', { mode: 'number' }).notNull(),\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\tendpoint_url: varchar('endpoint_url', { length: URL_LENGTH }).notNull(),\n\t\tid: varchar('id', { length: ID_LENGTH }).primaryKey(),\n\t\tlast_error: text('last_error'),\n\t\tlast_status: bigint('last_status', { mode: 'number' }),\n\t\tlogout_token: text('logout_token').notNull(),\n\t\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n\t}\n);\nexport const oauthPushedAuthorizationRequestsTable = pgTable(\n\t'auth_oauth_pushed_authorization_requests',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tparams_json: jsonb('params_json')\n\t\t\t.$type<Record<string, string>>()\n\t\t\t.notNull(),\n\t\trequest_uri_hash: varchar('request_uri_hash', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey()\n\t}\n);\nexport const oauthRefreshTokensTable = pgTable('auth_oauth_refresh_tokens', {\n\tacr: varchar('acr', { length: ID_LENGTH }),\n\taudience: varchar('audience', { length: URL_LENGTH }),\n\tclaims_json: jsonb('claims_json').$type<Record<string, unknown>>(),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tdpop_jkt: varchar('dpop_jkt', { length: ID_LENGTH }),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\tscopes: text('scopes').array().notNull(),\n\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey(),\n\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n});\n\ntype ClientRow = typeof oauthClientsTable.$inferSelect;\ntype CodeRow = typeof oauthCodesTable.$inferSelect;\ntype DeviceAuthRow = typeof oauthDeviceAuthorizationsTable.$inferSelect;\ntype LogoutDeliveryRow = typeof oauthLogoutDeliveriesTable.$inferSelect;\ntype RefreshRow = typeof oauthRefreshTokensTable.$inferSelect;\n\nconst toClient = (row: ClientRow): OAuthClient => ({\n\tbackchannelLogoutUri: row.backchannel_logout_uri ?? undefined,\n\tclientId: row.client_id,\n\thashedSecret: row.hashed_secret ?? undefined,\n\tjwks: row.jwks_json ?? undefined,\n\tjwksUri: row.jwks_uri ?? undefined,\n\tname: row.name,\n\tpostLogoutRedirectUris: row.post_logout_redirect_uris ?? undefined,\n\tredirectUris: row.redirect_uris,\n\trequirePushedAuthorizationRequests:\n\t\trow.require_pushed_authorization_requests ?? undefined,\n\trequireSignedRequestObject: row.require_signed_request_object ?? undefined,\n\tscopes: row.scopes\n});\n\nconst toLogoutDelivery = (row: LogoutDeliveryRow): LogoutDelivery => ({\n\tattempts: row.attempts,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tendpointUrl: row.endpoint_url,\n\tid: row.id,\n\tlastError: row.last_error ?? undefined,\n\tlastStatus: row.last_status ?? undefined,\n\tlogoutToken: row.logout_token,\n\tuserId: row.user_id\n});\n\nconst toCode = (row: CodeRow): AuthorizationCode => ({\n\tacr: row.acr ?? undefined,\n\taudience: row.audience ?? undefined,\n\tclaims: row.claims_json ?? undefined,\n\tclientId: row.client_id,\n\tcodeChallenge: row.code_challenge,\n\tcodeHash: row.code_hash,\n\tcreatedAt: row.created_at_ms,\n\tdpopJkt: row.dpop_jkt ?? undefined,\n\texpiresAt: row.expires_at_ms,\n\tnonce: row.nonce ?? undefined,\n\tredirectUri: row.redirect_uri,\n\tscopes: row.scopes,\n\tuserId: row.user_id\n});\n\nconst toCodeValues = (\n\tcode: AuthorizationCode\n): typeof oauthCodesTable.$inferInsert => ({\n\tacr: code.acr ?? null,\n\taudience: code.audience ?? null,\n\tclaims_json: code.claims ?? null,\n\tclient_id: code.clientId,\n\tcode_challenge: code.codeChallenge,\n\tcode_hash: code.codeHash,\n\tcreated_at_ms: code.createdAt,\n\tdpop_jkt: code.dpopJkt ?? null,\n\texpires_at_ms: code.expiresAt,\n\tnonce: code.nonce ?? null,\n\tredirect_uri: code.redirectUri,\n\tscopes: code.scopes,\n\tuser_id: code.userId\n});\n\nconst toDeviceAuth = (row: DeviceAuthRow): DeviceAuthorization => ({\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tdeviceCodeHash: row.device_code_hash,\n\texpiresAt: row.expires_at_ms,\n\tintervalSeconds: row.interval_seconds,\n\tscopes: row.scopes,\n\tstatus: deviceAuthorizationStatus(row.status),\n\tuserCode: row.user_code,\n\tuserSub: row.user_sub ?? undefined\n});\n\nconst toRefresh = (row: RefreshRow): OidcRefreshToken => ({\n\tacr: row.acr ?? undefined,\n\taudience: row.audience ?? undefined,\n\tclaims: row.claims_json ?? undefined,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tdpopJkt: row.dpop_jkt ?? undefined,\n\texpiresAt: row.expires_at_ms,\n\tscopes: row.scopes,\n\ttokenHash: row.token_hash,\n\tuserId: row.user_id\n});\n\nconst toRefreshValues = (\n\ttoken: OidcRefreshToken\n): typeof oauthRefreshTokensTable.$inferInsert => ({\n\tacr: token.acr ?? null,\n\taudience: token.audience ?? null,\n\tclaims_json: token.claims ?? null,\n\tclient_id: token.clientId,\n\tcreated_at_ms: token.createdAt,\n\tdpop_jkt: token.dpopJkt ?? null,\n\texpires_at_ms: token.expiresAt,\n\tscopes: token.scopes,\n\ttoken_hash: token.tokenHash,\n\tuser_id: token.userId\n});\n\nexport const createNeonAuthorizationCodeStore = (databaseUrl: string) =>\n\tcreatePostgresAuthorizationCodeStore(createNeonDatabase(databaseUrl));\nexport const createNeonClientAssertionJtiStore = (databaseUrl: string) =>\n\tcreatePostgresClientAssertionJtiStore(createNeonDatabase(databaseUrl));\nexport const createNeonClientRegistrationTokenStore = (databaseUrl: string) =>\n\tcreatePostgresClientRegistrationTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonDeviceAuthorizationStore = (databaseUrl: string) =>\n\tcreatePostgresDeviceAuthorizationStore(createNeonDatabase(databaseUrl));\nexport const createNeonInitialAccessTokenStore = (databaseUrl: string) =>\n\tcreatePostgresInitialAccessTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonLogoutDeliveryStore = (databaseUrl: string) =>\n\tcreatePostgresLogoutDeliveryStore(createNeonDatabase(databaseUrl));\nexport const createNeonOAuthClientStore = (databaseUrl: string) =>\n\tcreatePostgresOAuthClientStore(createNeonDatabase(databaseUrl));\nexport const createNeonOidcRefreshTokenStore = (databaseUrl: string) =>\n\tcreatePostgresOidcRefreshTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonPushedAuthorizationRequestStore = (\n\tdatabaseUrl: string\n) =>\n\tcreatePostgresPushedAuthorizationRequestStore(\n\t\tcreateNeonDatabase(databaseUrl)\n\t);\nexport const createPostgresAuthorizationCodeStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): AuthorizationCodeStore => ({\n\tconsumeCode: async (codeHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthCodesTable)\n\t\t\t.where(eq(oauthCodesTable.code_hash, codeHash))\n\t\t\t.returning();\n\n\t\treturn row ? toCode(row) : undefined;\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthCodesTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthCodesTable.user_id, userId),\n\t\t\t\t\teq(oauthCodesTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({ codeHash: oauthCodesTable.code_hash });\n\n\t\treturn deleted.length;\n\t},\n\tsaveCode: async (code) => {\n\t\tawait db.insert(oauthCodesTable).values(toCodeValues(code));\n\t}\n});\nexport const createPostgresClientAssertionJtiStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): ClientAssertionJtiStore => ({\n\trecordIfFresh: async (clientId, jti, expiresAt) => {\n\t\t// Lazy GC of expired entries on each call. Bounded by the assertion `exp` window\n\t\t// (~5 min), so this stays cheap even at high call volume.\n\t\tawait db\n\t\t\t.delete(oauthClientAssertionJtisTable)\n\t\t\t.where(lt(oauthClientAssertionJtisTable.expires_at_ms, Date.now()));\n\t\tconst compositeKey = `${clientId}|${jti}`;\n\t\ttry {\n\t\t\tawait db.insert(oauthClientAssertionJtisTable).values({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tcomposite_key: compositeKey,\n\t\t\t\texpires_at_ms: expiresAt,\n\t\t\t\tjti\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// PK collision = we've seen this jti for this client before = replay.\n\t\t\treturn false;\n\t\t}\n\t}\n});\nexport const createPostgresClientRegistrationTokenStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): ClientRegistrationTokenStore => ({\n\tdeleteByClientId: async (clientId) => {\n\t\tawait db\n\t\t\t.delete(oauthClientRegistrationTokensTable)\n\t\t\t.where(eq(oauthClientRegistrationTokensTable.client_id, clientId));\n\t},\n\tfindByTokenHash: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthClientRegistrationTokensTable)\n\t\t\t.where(eq(oauthClientRegistrationTokensTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\t\tif (!row) return undefined;\n\t\tconst token: ClientRegistrationToken = {\n\t\t\tclientId: row.client_id,\n\t\t\tcreatedAt: row.created_at_ms,\n\t\t\ttokenHash: row.token_hash\n\t\t};\n\n\t\treturn token;\n\t},\n\tsaveToken: async (token) => {\n\t\t// One reg token per client — drop any prior token before saving.\n\t\tawait db\n\t\t\t.delete(oauthClientRegistrationTokensTable)\n\t\t\t.where(\n\t\t\t\teq(oauthClientRegistrationTokensTable.client_id, token.clientId)\n\t\t\t);\n\t\tawait db.insert(oauthClientRegistrationTokensTable).values({\n\t\t\tclient_id: token.clientId,\n\t\t\tcreated_at_ms: token.createdAt,\n\t\t\ttoken_hash: token.tokenHash\n\t\t});\n\t}\n});\nexport const createPostgresDeviceAuthorizationStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): DeviceAuthorizationStore => ({\n\tdeleteByDeviceCodeHash: async (deviceCodeHash) => {\n\t\tawait db\n\t\t\t.delete(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t);\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthDeviceAuthorizationsTable.user_sub, userId),\n\t\t\t\t\teq(oauthDeviceAuthorizationsTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({\n\t\t\t\tdeviceCodeHash: oauthDeviceAuthorizationsTable.device_code_hash\n\t\t\t});\n\n\t\treturn deleted.length;\n\t},\n\tfindByDeviceCodeHash: async (deviceCodeHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t)\n\t\t\t.limit(1);\n\n\t\treturn row ? toDeviceAuth(row) : undefined;\n\t},\n\tfindByUserCode: async (userCode) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthDeviceAuthorizationsTable)\n\t\t\t.where(eq(oauthDeviceAuthorizationsTable.user_code, userCode))\n\t\t\t.limit(1);\n\n\t\treturn row ? toDeviceAuth(row) : undefined;\n\t},\n\tsaveDeviceAuthorization: async (deviceAuthorization) => {\n\t\tawait db.insert(oauthDeviceAuthorizationsTable).values({\n\t\t\tclient_id: deviceAuthorization.clientId,\n\t\t\tcreated_at_ms: deviceAuthorization.createdAt,\n\t\t\tdevice_code_hash: deviceAuthorization.deviceCodeHash,\n\t\t\texpires_at_ms: deviceAuthorization.expiresAt,\n\t\t\tinterval_seconds: deviceAuthorization.intervalSeconds,\n\t\t\tscopes: deviceAuthorization.scopes,\n\t\t\tstatus: deviceAuthorization.status,\n\t\t\tuser_code: deviceAuthorization.userCode,\n\t\t\tuser_sub: deviceAuthorization.userSub ?? null\n\t\t});\n\t},\n\tupdateStatus: async (deviceCodeHash, status, userSub) => {\n\t\tawait db\n\t\t\t.update(oauthDeviceAuthorizationsTable)\n\t\t\t.set({ status, user_sub: userSub ?? null })\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t);\n\t}\n});\nexport const createPostgresInitialAccessTokenStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): InitialAccessTokenStore => ({\n\tconsumeToken: async (tokenHash) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthInitialAccessTokensTable)\n\t\t\t.where(eq(oauthInitialAccessTokensTable.token_hash, tokenHash))\n\t\t\t.returning({\n\t\t\t\ttoken_hash: oauthInitialAccessTokensTable.token_hash\n\t\t\t});\n\n\t\treturn deleted.length > 0;\n\t}\n});\nexport const createPostgresLogoutDeliveryStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): LogoutDeliveryStore => ({\n\tlistFailed: async (limit = DEFAULT_LIST_LIMIT) => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(oauthLogoutDeliveriesTable)\n\t\t\t.orderBy(desc(oauthLogoutDeliveriesTable.created_at_ms))\n\t\t\t.limit(limit);\n\n\t\treturn rows.map(toLogoutDelivery);\n\t},\n\trecordFailure: async (delivery) => {\n\t\tawait db.insert(oauthLogoutDeliveriesTable).values({\n\t\t\tattempts: delivery.attempts,\n\t\t\tclient_id: delivery.clientId,\n\t\t\tcreated_at_ms: delivery.createdAt,\n\t\t\tendpoint_url: delivery.endpointUrl,\n\t\t\tid: delivery.id,\n\t\t\tlast_error: delivery.lastError ?? null,\n\t\t\tlast_status: delivery.lastStatus ?? null,\n\t\t\tlogout_token: delivery.logoutToken,\n\t\t\tuser_id: delivery.userId\n\t\t});\n\t},\n\tremoveFailure: async (deliveryId) => {\n\t\tawait db\n\t\t\t.delete(oauthLogoutDeliveriesTable)\n\t\t\t.where(eq(oauthLogoutDeliveriesTable.id, deliveryId));\n\t}\n});\n// Maps an OAuthClient domain object to the row shape (used by saveClient + updateClient).\nconst toClientValues = (\n\tclient: OAuthClient\n): typeof oauthClientsTable.$inferInsert => ({\n\tbackchannel_logout_uri: client.backchannelLogoutUri ?? null,\n\tclient_id: client.clientId,\n\thashed_secret: client.hashedSecret ?? null,\n\tjwks_json: client.jwks ?? null,\n\tjwks_uri: client.jwksUri ?? null,\n\tname: client.name,\n\tpost_logout_redirect_uris: client.postLogoutRedirectUris ?? null,\n\tredirect_uris: client.redirectUris,\n\trequire_pushed_authorization_requests:\n\t\tclient.requirePushedAuthorizationRequests ?? null,\n\trequire_signed_request_object: client.requireSignedRequestObject ?? null,\n\tscopes: client.scopes\n});\n\nexport const createPostgresOAuthClientStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): OAuthClientStore => ({\n\tdeleteClient: async (clientId) => {\n\t\tawait db\n\t\t\t.delete(oauthClientsTable)\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId));\n\t},\n\tfindClient: async (clientId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthClientsTable)\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toClient(row) : undefined;\n\t},\n\tsaveClient: async (client) => {\n\t\tawait db.insert(oauthClientsTable).values(toClientValues(client));\n\t},\n\tupdateClient: async (clientId, client) => {\n\t\tawait db\n\t\t\t.update(oauthClientsTable)\n\t\t\t.set(toClientValues(client))\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId));\n\t}\n});\nexport const createPostgresOidcRefreshTokenStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): OidcRefreshTokenStore => ({\n\tconsumeToken: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.token_hash, tokenHash))\n\t\t\t.returning();\n\n\t\treturn row ? toRefresh(row) : undefined;\n\t},\n\tdeleteForUser: async (userId) => {\n\t\tawait db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.user_id, userId));\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthRefreshTokensTable.user_id, userId),\n\t\t\t\t\teq(oauthRefreshTokensTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({ tokenHash: oauthRefreshTokensTable.token_hash });\n\n\t\treturn deleted.length;\n\t},\n\tgetToken: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\n\t\treturn row ? toRefresh(row) : undefined;\n\t},\n\tlistClientIdsForUser: async (userId) => {\n\t\tconst rows = await db\n\t\t\t.selectDistinct({ client_id: oauthRefreshTokensTable.client_id })\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthRefreshTokensTable.user_id, userId),\n\t\t\t\t\tgt(oauthRefreshTokensTable.expires_at_ms, Date.now())\n\t\t\t\t)\n\t\t\t);\n\n\t\treturn rows.map((row) => row.client_id);\n\t},\n\tlistConnections: async () => {\n\t\tconst rows = await db\n\t\t\t.selectDistinct({\n\t\t\t\tclientId: oauthRefreshTokensTable.client_id,\n\t\t\t\tuserId: oauthRefreshTokensTable.user_id\n\t\t\t})\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(gt(oauthRefreshTokensTable.expires_at_ms, Date.now()));\n\n\t\treturn rows;\n\t},\n\tsaveToken: async (token) => {\n\t\tawait db.insert(oauthRefreshTokensTable).values(toRefreshValues(token));\n\t}\n});\nexport const createPostgresPushedAuthorizationRequestStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): PushedAuthorizationRequestStore => ({\n\tconsumeRequest: async (requestUriHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthPushedAuthorizationRequestsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthPushedAuthorizationRequestsTable.request_uri_hash,\n\t\t\t\t\trequestUriHash\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning();\n\t\tif (!row) return undefined;\n\t\t// Lazy GC of expired rows from prior writes — keeps the table thin without a\n\t\t// dedicated cron. Same call returns undefined if THIS request was already expired.\n\t\tawait db\n\t\t\t.delete(oauthPushedAuthorizationRequestsTable)\n\t\t\t.where(\n\t\t\t\tlt(\n\t\t\t\t\toauthPushedAuthorizationRequestsTable.expires_at_ms,\n\t\t\t\t\tDate.now()\n\t\t\t\t)\n\t\t\t);\n\t\tif (row.expires_at_ms < Date.now()) return undefined;\n\t\tconst request: PushedAuthorizationRequest = {\n\t\t\tclientId: row.client_id,\n\t\t\tcreatedAt: row.created_at_ms,\n\t\t\texpiresAt: row.expires_at_ms,\n\t\t\tparams: row.params_json,\n\t\t\trequestUriHash: row.request_uri_hash\n\t\t};\n\n\t\treturn request;\n\t},\n\tsaveRequest: async (request) => {\n\t\tawait db.insert(oauthPushedAuthorizationRequestsTable).values({\n\t\t\tclient_id: request.clientId,\n\t\t\tcreated_at_ms: request.createdAt,\n\t\t\texpires_at_ms: request.expiresAt,\n\t\t\tparams_json: request.params,\n\t\t\trequest_uri_hash: request.requestUriHash\n\t\t});\n\t}\n});\n\ntype BackchannelAuthRow = typeof oauthBackchannelAuthRequestsTable.$inferSelect;\n\nconst toBackchannelAuth = (\n\trow: BackchannelAuthRow\n): BackchannelAuthRequest => ({\n\tauthReqId: row.auth_req_id,\n\tbindingMessage: row.binding_message ?? undefined,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\texpiresAt: row.expires_at_ms,\n\tintervalSeconds: row.interval_seconds,\n\tlastPolledAt: row.last_polled_at_ms ?? undefined,\n\tscopes: row.scopes,\n\tstatus: backchannelAuthStatus(row.status),\n\tuserSub: row.user_sub ?? undefined\n});\n\nexport const createNeonBackchannelAuthStore = (databaseUrl: string) =>\n\tcreatePostgresBackchannelAuthStore(createNeonDatabase(databaseUrl));\n\nexport const createPostgresBackchannelAuthStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): BackchannelAuthStore => ({\n\tdeleteByAuthReqId: async (authReqId) => {\n\t\tawait db\n\t\t\t.delete(oauthBackchannelAuthRequestsTable)\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t},\n\tfindByAuthReqId: async (authReqId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthBackchannelAuthRequestsTable)\n\t\t\t.where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toBackchannelAuth(row) : undefined;\n\t},\n\trecordPoll: async (authReqId, polledAt) => {\n\t\tawait db\n\t\t\t.update(oauthBackchannelAuthRequestsTable)\n\t\t\t.set({ last_polled_at_ms: polledAt })\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t},\n\tsaveBackchannelAuth: async (request) => {\n\t\tawait db.insert(oauthBackchannelAuthRequestsTable).values({\n\t\t\tauth_req_id: request.authReqId,\n\t\t\tbinding_message: request.bindingMessage ?? null,\n\t\t\tclient_id: request.clientId,\n\t\t\tcreated_at_ms: request.createdAt,\n\t\t\texpires_at_ms: request.expiresAt,\n\t\t\tinterval_seconds: request.intervalSeconds,\n\t\t\tlast_polled_at_ms: request.lastPolledAt ?? null,\n\t\t\tscopes: request.scopes,\n\t\t\tstatus: request.status,\n\t\t\tuser_sub: request.userSub ?? null\n\t\t});\n\t},\n\tupdateStatus: async (authReqId, status, userSub) => {\n\t\tawait db\n\t\t\t.update(oauthBackchannelAuthRequestsTable)\n\t\t\t.set({ status, user_sub: userSub ?? null })\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t}\n});\n",
|
|
22
|
+
"import { and, desc, eq, gt, lt } from 'drizzle-orm';\nimport {\n\tbigint,\n\tboolean,\n\tjsonb,\n\tpgTable,\n\ttext,\n\tvarchar\n} from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type {\n\tAuthorizationCode,\n\tAuthorizationCodeStore,\n\tBackchannelAuthRequest,\n\tBackchannelAuthStatus,\n\tBackchannelAuthStore,\n\tClientAssertionJtiStore,\n\tClientRegistrationToken,\n\tClientRegistrationTokenStore,\n\tDeviceAuthorization,\n\tDeviceAuthorizationStatus,\n\tDeviceAuthorizationStore,\n\tInitialAccessTokenStore,\n\tLogoutDelivery,\n\tLogoutDeliveryStore,\n\tOAuthClient,\n\tOAuthClientStore,\n\tOidcRefreshToken,\n\tOidcRefreshTokenStore,\n\tPushedAuthorizationRequest,\n\tPushedAuthorizationRequestStore\n} from './types';\n\nconst URL_LENGTH = 2048;\nconst DEFAULT_LIST_LIMIT = 100;\n\nconst ID_LENGTH = 255;\nconst BACKCHANNEL_AUTH_STATUSES = new Set<BackchannelAuthStatus>([\n\t'approved',\n\t'denied',\n\t'pending'\n]);\nconst DEVICE_AUTHORIZATION_STATUSES = new Set<DeviceAuthorizationStatus>([\n\t'approved',\n\t'denied',\n\t'pending'\n]);\n\nconst backchannelAuthStatus = (value: string) => {\n\tfor (const status of BACKCHANNEL_AUTH_STATUSES)\n\t\tif (status === value) return status;\n\n\tthrow new Error(\n\t\t`Invalid persisted backchannel authorization status: ${value}`\n\t);\n};\n\nconst deviceAuthorizationStatus = (value: string) => {\n\tfor (const status of DEVICE_AUTHORIZATION_STATUSES)\n\t\tif (status === value) return status;\n\n\tthrow new Error(`Invalid persisted device authorization status: ${value}`);\n};\n\nexport const oauthBackchannelAuthRequestsTable = pgTable(\n\t'auth_oauth_backchannel_auth_requests',\n\t{\n\t\tauth_req_id: varchar('auth_req_id', { length: ID_LENGTH }).primaryKey(),\n\t\tbinding_message: text('binding_message'),\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tinterval_seconds: bigint('interval_seconds', {\n\t\t\tmode: 'number'\n\t\t}).notNull(),\n\t\tlast_polled_at_ms: bigint('last_polled_at_ms', { mode: 'number' }),\n\t\tscopes: text('scopes').array().notNull(),\n\t\tstatus: varchar('status', { length: 16 }).notNull(),\n\t\tuser_sub: varchar('user_sub', { length: ID_LENGTH })\n\t}\n);\nexport const oauthClientAssertionJtisTable = pgTable(\n\t'auth_oauth_client_assertion_jtis',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcomposite_key: varchar('composite_key', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tjti: varchar('jti', { length: ID_LENGTH }).notNull()\n\t}\n);\nexport const oauthClientRegistrationTokensTable = pgTable(\n\t'auth_oauth_client_registration_tokens',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey()\n\t}\n);\nexport const oauthClientsTable = pgTable('auth_oauth_clients', {\n\tbackchannel_logout_uri: varchar('backchannel_logout_uri', {\n\t\tlength: URL_LENGTH\n\t}),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).primaryKey(),\n\thashed_secret: varchar('hashed_secret', { length: ID_LENGTH }),\n\tjwks_json: jsonb('jwks_json').$type<JsonWebKey[]>(),\n\tjwks_uri: varchar('jwks_uri', { length: URL_LENGTH }),\n\tname: varchar('name', { length: ID_LENGTH }).notNull(),\n\tpost_logout_redirect_uris: text('post_logout_redirect_uris').array(),\n\tredirect_uris: text('redirect_uris').array().notNull(),\n\trequire_pushed_authorization_requests: boolean(\n\t\t'require_pushed_authorization_requests'\n\t),\n\trequire_signed_request_object: boolean('require_signed_request_object'),\n\tscopes: text('scopes').array().notNull()\n});\nexport const oauthCodesTable = pgTable('auth_oauth_codes', {\n\tacr: varchar('acr', { length: ID_LENGTH }),\n\taudience: varchar('audience', { length: URL_LENGTH }),\n\tclaims_json: jsonb('claims_json').$type<Record<string, unknown>>(),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\tcode_challenge: varchar('code_challenge', { length: ID_LENGTH }).notNull(),\n\tcode_hash: varchar('code_hash', { length: ID_LENGTH }).primaryKey(),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tdpop_jkt: varchar('dpop_jkt', { length: ID_LENGTH }),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\tnonce: varchar('nonce', { length: ID_LENGTH }),\n\tredirect_uri: varchar('redirect_uri', { length: ID_LENGTH }).notNull(),\n\tscopes: text('scopes').array().notNull(),\n\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n});\nexport const oauthDeviceAuthorizationsTable = pgTable(\n\t'auth_oauth_device_authorizations',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\tdevice_code_hash: varchar('device_code_hash', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tinterval_seconds: bigint('interval_seconds', {\n\t\t\tmode: 'number'\n\t\t}).notNull(),\n\t\tscopes: text('scopes').array().notNull(),\n\t\tstatus: varchar('status', { length: 16 }).notNull(),\n\t\tuser_code: varchar('user_code', { length: 16 }).notNull().unique(),\n\t\tuser_sub: varchar('user_sub', { length: ID_LENGTH })\n\t}\n);\nexport const oauthInitialAccessTokensTable = pgTable(\n\t'auth_oauth_initial_access_tokens',\n\t{\n\t\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey()\n\t}\n);\nexport const oauthLogoutDeliveriesTable = pgTable(\n\t'auth_oauth_logout_deliveries',\n\t{\n\t\tattempts: bigint('attempts', { mode: 'number' }).notNull(),\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\tendpoint_url: varchar('endpoint_url', { length: URL_LENGTH }).notNull(),\n\t\tid: varchar('id', { length: ID_LENGTH }).primaryKey(),\n\t\tlast_error: text('last_error'),\n\t\tlast_status: bigint('last_status', { mode: 'number' }),\n\t\tlogout_token: text('logout_token').notNull(),\n\t\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n\t}\n);\nexport const oauthPushedAuthorizationRequestsTable = pgTable(\n\t'auth_oauth_pushed_authorization_requests',\n\t{\n\t\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tparams_json: jsonb('params_json')\n\t\t\t.$type<Record<string, string>>()\n\t\t\t.notNull(),\n\t\trequest_uri_hash: varchar('request_uri_hash', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey()\n\t}\n);\nexport const oauthRefreshTokensTable = pgTable('auth_oauth_refresh_tokens', {\n\tacr: varchar('acr', { length: ID_LENGTH }),\n\taudience: varchar('audience', { length: URL_LENGTH }),\n\tclaims_json: jsonb('claims_json').$type<Record<string, unknown>>(),\n\tclient_id: varchar('client_id', { length: ID_LENGTH }).notNull(),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tdpop_jkt: varchar('dpop_jkt', { length: ID_LENGTH }),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\tscopes: text('scopes').array().notNull(),\n\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey(),\n\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n});\n\ntype ClientRow = typeof oauthClientsTable.$inferSelect;\ntype CodeRow = typeof oauthCodesTable.$inferSelect;\ntype DeviceAuthRow = typeof oauthDeviceAuthorizationsTable.$inferSelect;\ntype LogoutDeliveryRow = typeof oauthLogoutDeliveriesTable.$inferSelect;\ntype RefreshRow = typeof oauthRefreshTokensTable.$inferSelect;\n\nconst toClient = (row: ClientRow): OAuthClient => ({\n\tbackchannelLogoutUri: row.backchannel_logout_uri ?? undefined,\n\tclientId: row.client_id,\n\thashedSecret: row.hashed_secret ?? undefined,\n\tjwks: row.jwks_json ?? undefined,\n\tjwksUri: row.jwks_uri ?? undefined,\n\tname: row.name,\n\tpostLogoutRedirectUris: row.post_logout_redirect_uris ?? undefined,\n\tredirectUris: row.redirect_uris,\n\trequirePushedAuthorizationRequests:\n\t\trow.require_pushed_authorization_requests ?? undefined,\n\trequireSignedRequestObject: row.require_signed_request_object ?? undefined,\n\tscopes: row.scopes\n});\n\nconst toLogoutDelivery = (row: LogoutDeliveryRow): LogoutDelivery => ({\n\tattempts: row.attempts,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tendpointUrl: row.endpoint_url,\n\tid: row.id,\n\tlastError: row.last_error ?? undefined,\n\tlastStatus: row.last_status ?? undefined,\n\tlogoutToken: row.logout_token,\n\tuserId: row.user_id\n});\n\nconst toCode = (row: CodeRow): AuthorizationCode => ({\n\tacr: row.acr ?? undefined,\n\taudience: row.audience ?? undefined,\n\tclaims: row.claims_json ?? undefined,\n\tclientId: row.client_id,\n\tcodeChallenge: row.code_challenge,\n\tcodeHash: row.code_hash,\n\tcreatedAt: row.created_at_ms,\n\tdpopJkt: row.dpop_jkt ?? undefined,\n\texpiresAt: row.expires_at_ms,\n\tnonce: row.nonce ?? undefined,\n\tredirectUri: row.redirect_uri,\n\tscopes: row.scopes,\n\tuserId: row.user_id\n});\n\nconst toCodeValues = (\n\tcode: AuthorizationCode\n): typeof oauthCodesTable.$inferInsert => ({\n\tacr: code.acr ?? null,\n\taudience: code.audience ?? null,\n\tclaims_json: code.claims ?? null,\n\tclient_id: code.clientId,\n\tcode_challenge: code.codeChallenge,\n\tcode_hash: code.codeHash,\n\tcreated_at_ms: code.createdAt,\n\tdpop_jkt: code.dpopJkt ?? null,\n\texpires_at_ms: code.expiresAt,\n\tnonce: code.nonce ?? null,\n\tredirect_uri: code.redirectUri,\n\tscopes: code.scopes,\n\tuser_id: code.userId\n});\n\nconst toDeviceAuth = (row: DeviceAuthRow): DeviceAuthorization => ({\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tdeviceCodeHash: row.device_code_hash,\n\texpiresAt: row.expires_at_ms,\n\tintervalSeconds: row.interval_seconds,\n\tscopes: row.scopes,\n\tstatus: deviceAuthorizationStatus(row.status),\n\tuserCode: row.user_code,\n\tuserSub: row.user_sub ?? undefined\n});\n\nconst toRefresh = (row: RefreshRow): OidcRefreshToken => ({\n\tacr: row.acr ?? undefined,\n\taudience: row.audience ?? undefined,\n\tclaims: row.claims_json ?? undefined,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\tdpopJkt: row.dpop_jkt ?? undefined,\n\texpiresAt: row.expires_at_ms,\n\tscopes: row.scopes,\n\ttokenHash: row.token_hash,\n\tuserId: row.user_id\n});\n\nconst toRefreshValues = (\n\ttoken: OidcRefreshToken\n): typeof oauthRefreshTokensTable.$inferInsert => ({\n\tacr: token.acr ?? null,\n\taudience: token.audience ?? null,\n\tclaims_json: token.claims ?? null,\n\tclient_id: token.clientId,\n\tcreated_at_ms: token.createdAt,\n\tdpop_jkt: token.dpopJkt ?? null,\n\texpires_at_ms: token.expiresAt,\n\tscopes: token.scopes,\n\ttoken_hash: token.tokenHash,\n\tuser_id: token.userId\n});\n\nexport const createNeonAuthorizationCodeStore = (databaseUrl: string) =>\n\tcreatePostgresAuthorizationCodeStore(createNeonDatabase(databaseUrl));\nexport const createNeonClientAssertionJtiStore = (databaseUrl: string) =>\n\tcreatePostgresClientAssertionJtiStore(createNeonDatabase(databaseUrl));\nexport const createNeonClientRegistrationTokenStore = (databaseUrl: string) =>\n\tcreatePostgresClientRegistrationTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonDeviceAuthorizationStore = (databaseUrl: string) =>\n\tcreatePostgresDeviceAuthorizationStore(createNeonDatabase(databaseUrl));\nexport const createNeonInitialAccessTokenStore = (databaseUrl: string) =>\n\tcreatePostgresInitialAccessTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonLogoutDeliveryStore = (databaseUrl: string) =>\n\tcreatePostgresLogoutDeliveryStore(createNeonDatabase(databaseUrl));\nexport const createNeonOAuthClientStore = (databaseUrl: string) =>\n\tcreatePostgresOAuthClientStore(createNeonDatabase(databaseUrl));\nexport const createNeonOidcRefreshTokenStore = (databaseUrl: string) =>\n\tcreatePostgresOidcRefreshTokenStore(createNeonDatabase(databaseUrl));\nexport const createNeonPushedAuthorizationRequestStore = (\n\tdatabaseUrl: string\n) =>\n\tcreatePostgresPushedAuthorizationRequestStore(\n\t\tcreateNeonDatabase(databaseUrl)\n\t);\nexport const createPostgresAuthorizationCodeStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): AuthorizationCodeStore => ({\n\tconsumeCode: async (codeHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthCodesTable)\n\t\t\t.where(eq(oauthCodesTable.code_hash, codeHash))\n\t\t\t.returning();\n\n\t\treturn row ? toCode(row) : undefined;\n\t},\n\tdeleteForClient: async (clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthCodesTable)\n\t\t\t.where(eq(oauthCodesTable.client_id, clientId))\n\t\t\t.returning({ codeHash: oauthCodesTable.code_hash });\n\n\t\treturn deleted.length;\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthCodesTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthCodesTable.user_id, userId),\n\t\t\t\t\teq(oauthCodesTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({ codeHash: oauthCodesTable.code_hash });\n\n\t\treturn deleted.length;\n\t},\n\tsaveCode: async (code) => {\n\t\tawait db.insert(oauthCodesTable).values(toCodeValues(code));\n\t}\n});\nexport const createPostgresClientAssertionJtiStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): ClientAssertionJtiStore => ({\n\trecordIfFresh: async (clientId, jti, expiresAt) => {\n\t\t// Lazy GC of expired entries on each call. Bounded by the assertion `exp` window\n\t\t// (~5 min), so this stays cheap even at high call volume.\n\t\tawait db\n\t\t\t.delete(oauthClientAssertionJtisTable)\n\t\t\t.where(lt(oauthClientAssertionJtisTable.expires_at_ms, Date.now()));\n\t\tconst compositeKey = `${clientId}|${jti}`;\n\t\ttry {\n\t\t\tawait db.insert(oauthClientAssertionJtisTable).values({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tcomposite_key: compositeKey,\n\t\t\t\texpires_at_ms: expiresAt,\n\t\t\t\tjti\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// PK collision = we've seen this jti for this client before = replay.\n\t\t\treturn false;\n\t\t}\n\t}\n});\nexport const createPostgresClientRegistrationTokenStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): ClientRegistrationTokenStore => ({\n\tdeleteByClientId: async (clientId) => {\n\t\tawait db\n\t\t\t.delete(oauthClientRegistrationTokensTable)\n\t\t\t.where(eq(oauthClientRegistrationTokensTable.client_id, clientId));\n\t},\n\tdeleteForClient: async (clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthClientRegistrationTokensTable)\n\t\t\t.where(eq(oauthClientRegistrationTokensTable.client_id, clientId))\n\t\t\t.returning({\n\t\t\t\ttokenHash: oauthClientRegistrationTokensTable.token_hash\n\t\t\t});\n\n\t\treturn deleted.length;\n\t},\n\tfindByTokenHash: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthClientRegistrationTokensTable)\n\t\t\t.where(eq(oauthClientRegistrationTokensTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\t\tif (!row) return undefined;\n\t\tconst token: ClientRegistrationToken = {\n\t\t\tclientId: row.client_id,\n\t\t\tcreatedAt: row.created_at_ms,\n\t\t\ttokenHash: row.token_hash\n\t\t};\n\n\t\treturn token;\n\t},\n\tsaveToken: async (token) => {\n\t\t// One reg token per client — drop any prior token before saving.\n\t\tawait db\n\t\t\t.delete(oauthClientRegistrationTokensTable)\n\t\t\t.where(\n\t\t\t\teq(oauthClientRegistrationTokensTable.client_id, token.clientId)\n\t\t\t);\n\t\tawait db.insert(oauthClientRegistrationTokensTable).values({\n\t\t\tclient_id: token.clientId,\n\t\t\tcreated_at_ms: token.createdAt,\n\t\t\ttoken_hash: token.tokenHash\n\t\t});\n\t}\n});\nexport const createPostgresDeviceAuthorizationStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): DeviceAuthorizationStore => ({\n\tdeleteByDeviceCodeHash: async (deviceCodeHash) => {\n\t\tawait db\n\t\t\t.delete(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t);\n\t},\n\tdeleteForClient: async (clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthDeviceAuthorizationsTable)\n\t\t\t.where(eq(oauthDeviceAuthorizationsTable.client_id, clientId))\n\t\t\t.returning({\n\t\t\t\tdeviceCodeHash: oauthDeviceAuthorizationsTable.device_code_hash\n\t\t\t});\n\n\t\treturn deleted.length;\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthDeviceAuthorizationsTable.user_sub, userId),\n\t\t\t\t\teq(oauthDeviceAuthorizationsTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({\n\t\t\t\tdeviceCodeHash: oauthDeviceAuthorizationsTable.device_code_hash\n\t\t\t});\n\n\t\treturn deleted.length;\n\t},\n\tfindByDeviceCodeHash: async (deviceCodeHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthDeviceAuthorizationsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t)\n\t\t\t.limit(1);\n\n\t\treturn row ? toDeviceAuth(row) : undefined;\n\t},\n\tfindByUserCode: async (userCode) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthDeviceAuthorizationsTable)\n\t\t\t.where(eq(oauthDeviceAuthorizationsTable.user_code, userCode))\n\t\t\t.limit(1);\n\n\t\treturn row ? toDeviceAuth(row) : undefined;\n\t},\n\tsaveDeviceAuthorization: async (deviceAuthorization) => {\n\t\tawait db.insert(oauthDeviceAuthorizationsTable).values({\n\t\t\tclient_id: deviceAuthorization.clientId,\n\t\t\tcreated_at_ms: deviceAuthorization.createdAt,\n\t\t\tdevice_code_hash: deviceAuthorization.deviceCodeHash,\n\t\t\texpires_at_ms: deviceAuthorization.expiresAt,\n\t\t\tinterval_seconds: deviceAuthorization.intervalSeconds,\n\t\t\tscopes: deviceAuthorization.scopes,\n\t\t\tstatus: deviceAuthorization.status,\n\t\t\tuser_code: deviceAuthorization.userCode,\n\t\t\tuser_sub: deviceAuthorization.userSub ?? null\n\t\t});\n\t},\n\tupdateStatus: async (deviceCodeHash, status, userSub) => {\n\t\tawait db\n\t\t\t.update(oauthDeviceAuthorizationsTable)\n\t\t\t.set({ status, user_sub: userSub ?? null })\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthDeviceAuthorizationsTable.device_code_hash,\n\t\t\t\t\tdeviceCodeHash\n\t\t\t\t)\n\t\t\t);\n\t}\n});\nexport const createPostgresInitialAccessTokenStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): InitialAccessTokenStore => ({\n\tconsumeToken: async (tokenHash) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthInitialAccessTokensTable)\n\t\t\t.where(eq(oauthInitialAccessTokensTable.token_hash, tokenHash))\n\t\t\t.returning({\n\t\t\t\ttoken_hash: oauthInitialAccessTokensTable.token_hash\n\t\t\t});\n\n\t\treturn deleted.length > 0;\n\t}\n});\nexport const createPostgresLogoutDeliveryStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): LogoutDeliveryStore => ({\n\tlistFailed: async (limit = DEFAULT_LIST_LIMIT) => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(oauthLogoutDeliveriesTable)\n\t\t\t.orderBy(desc(oauthLogoutDeliveriesTable.created_at_ms))\n\t\t\t.limit(limit);\n\n\t\treturn rows.map(toLogoutDelivery);\n\t},\n\trecordFailure: async (delivery) => {\n\t\tawait db.insert(oauthLogoutDeliveriesTable).values({\n\t\t\tattempts: delivery.attempts,\n\t\t\tclient_id: delivery.clientId,\n\t\t\tcreated_at_ms: delivery.createdAt,\n\t\t\tendpoint_url: delivery.endpointUrl,\n\t\t\tid: delivery.id,\n\t\t\tlast_error: delivery.lastError ?? null,\n\t\t\tlast_status: delivery.lastStatus ?? null,\n\t\t\tlogout_token: delivery.logoutToken,\n\t\t\tuser_id: delivery.userId\n\t\t});\n\t},\n\tremoveFailure: async (deliveryId) => {\n\t\tawait db\n\t\t\t.delete(oauthLogoutDeliveriesTable)\n\t\t\t.where(eq(oauthLogoutDeliveriesTable.id, deliveryId));\n\t}\n});\n// Maps an OAuthClient domain object to the row shape (used by saveClient + updateClient).\nconst toClientValues = (\n\tclient: OAuthClient\n): typeof oauthClientsTable.$inferInsert => ({\n\tbackchannel_logout_uri: client.backchannelLogoutUri ?? null,\n\tclient_id: client.clientId,\n\thashed_secret: client.hashedSecret ?? null,\n\tjwks_json: client.jwks ?? null,\n\tjwks_uri: client.jwksUri ?? null,\n\tname: client.name,\n\tpost_logout_redirect_uris: client.postLogoutRedirectUris ?? null,\n\tredirect_uris: client.redirectUris,\n\trequire_pushed_authorization_requests:\n\t\tclient.requirePushedAuthorizationRequests ?? null,\n\trequire_signed_request_object: client.requireSignedRequestObject ?? null,\n\tscopes: client.scopes\n});\n\nexport const createPostgresOAuthClientStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): OAuthClientStore => ({\n\tdeleteClient: async (clientId) => {\n\t\tawait db\n\t\t\t.delete(oauthClientsTable)\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId));\n\t},\n\tfindClient: async (clientId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthClientsTable)\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toClient(row) : undefined;\n\t},\n\tsaveClient: async (client) => {\n\t\tawait db.insert(oauthClientsTable).values(toClientValues(client));\n\t},\n\tupdateClient: async (clientId, client) => {\n\t\tawait db\n\t\t\t.update(oauthClientsTable)\n\t\t\t.set(toClientValues(client))\n\t\t\t.where(eq(oauthClientsTable.client_id, clientId));\n\t}\n});\nexport const createPostgresOidcRefreshTokenStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): OidcRefreshTokenStore => ({\n\tconsumeToken: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.token_hash, tokenHash))\n\t\t\t.returning();\n\n\t\treturn row ? toRefresh(row) : undefined;\n\t},\n\tdeleteForClient: async (clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.client_id, clientId))\n\t\t\t.returning({ tokenHash: oauthRefreshTokensTable.token_hash });\n\n\t\treturn deleted.length;\n\t},\n\tdeleteForUser: async (userId) => {\n\t\tawait db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.user_id, userId));\n\t},\n\tdeleteForUserClient: async (userId, clientId) => {\n\t\tconst deleted = await db\n\t\t\t.delete(oauthRefreshTokensTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthRefreshTokensTable.user_id, userId),\n\t\t\t\t\teq(oauthRefreshTokensTable.client_id, clientId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning({ tokenHash: oauthRefreshTokensTable.token_hash });\n\n\t\treturn deleted.length;\n\t},\n\tgetToken: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(eq(oauthRefreshTokensTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\n\t\treturn row ? toRefresh(row) : undefined;\n\t},\n\tlistClientIdsForUser: async (userId) => {\n\t\tconst rows = await db\n\t\t\t.selectDistinct({ client_id: oauthRefreshTokensTable.client_id })\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(oauthRefreshTokensTable.user_id, userId),\n\t\t\t\t\tgt(oauthRefreshTokensTable.expires_at_ms, Date.now())\n\t\t\t\t)\n\t\t\t);\n\n\t\treturn rows.map((row) => row.client_id);\n\t},\n\tlistConnections: async () => {\n\t\tconst rows = await db\n\t\t\t.selectDistinct({\n\t\t\t\tclientId: oauthRefreshTokensTable.client_id,\n\t\t\t\tuserId: oauthRefreshTokensTable.user_id\n\t\t\t})\n\t\t\t.from(oauthRefreshTokensTable)\n\t\t\t.where(gt(oauthRefreshTokensTable.expires_at_ms, Date.now()));\n\n\t\treturn rows;\n\t},\n\tsaveToken: async (token) => {\n\t\tawait db.insert(oauthRefreshTokensTable).values(toRefreshValues(token));\n\t}\n});\nexport const createPostgresPushedAuthorizationRequestStore = <\n\tDB extends AnyPgDatabase\n>(\n\tdb: DB\n): PushedAuthorizationRequestStore => ({\n\tconsumeRequest: async (requestUriHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(oauthPushedAuthorizationRequestsTable)\n\t\t\t.where(\n\t\t\t\teq(\n\t\t\t\t\toauthPushedAuthorizationRequestsTable.request_uri_hash,\n\t\t\t\t\trequestUriHash\n\t\t\t\t)\n\t\t\t)\n\t\t\t.returning();\n\t\tif (!row) return undefined;\n\t\t// Lazy GC of expired rows from prior writes — keeps the table thin without a\n\t\t// dedicated cron. Same call returns undefined if THIS request was already expired.\n\t\tawait db\n\t\t\t.delete(oauthPushedAuthorizationRequestsTable)\n\t\t\t.where(\n\t\t\t\tlt(\n\t\t\t\t\toauthPushedAuthorizationRequestsTable.expires_at_ms,\n\t\t\t\t\tDate.now()\n\t\t\t\t)\n\t\t\t);\n\t\tif (row.expires_at_ms < Date.now()) return undefined;\n\t\tconst request: PushedAuthorizationRequest = {\n\t\t\tclientId: row.client_id,\n\t\t\tcreatedAt: row.created_at_ms,\n\t\t\texpiresAt: row.expires_at_ms,\n\t\t\tparams: row.params_json,\n\t\t\trequestUriHash: row.request_uri_hash\n\t\t};\n\n\t\treturn request;\n\t},\n\tsaveRequest: async (request) => {\n\t\tawait db.insert(oauthPushedAuthorizationRequestsTable).values({\n\t\t\tclient_id: request.clientId,\n\t\t\tcreated_at_ms: request.createdAt,\n\t\t\texpires_at_ms: request.expiresAt,\n\t\t\tparams_json: request.params,\n\t\t\trequest_uri_hash: request.requestUriHash\n\t\t});\n\t}\n});\n\ntype BackchannelAuthRow = typeof oauthBackchannelAuthRequestsTable.$inferSelect;\n\nconst toBackchannelAuth = (\n\trow: BackchannelAuthRow\n): BackchannelAuthRequest => ({\n\tauthReqId: row.auth_req_id,\n\tbindingMessage: row.binding_message ?? undefined,\n\tclientId: row.client_id,\n\tcreatedAt: row.created_at_ms,\n\texpiresAt: row.expires_at_ms,\n\tintervalSeconds: row.interval_seconds,\n\tlastPolledAt: row.last_polled_at_ms ?? undefined,\n\tscopes: row.scopes,\n\tstatus: backchannelAuthStatus(row.status),\n\tuserSub: row.user_sub ?? undefined\n});\n\nexport const createNeonBackchannelAuthStore = (databaseUrl: string) =>\n\tcreatePostgresBackchannelAuthStore(createNeonDatabase(databaseUrl));\n\nexport const createPostgresBackchannelAuthStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): BackchannelAuthStore => ({\n\tdeleteByAuthReqId: async (authReqId) => {\n\t\tawait db\n\t\t\t.delete(oauthBackchannelAuthRequestsTable)\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t},\n\tfindByAuthReqId: async (authReqId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(oauthBackchannelAuthRequestsTable)\n\t\t\t.where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toBackchannelAuth(row) : undefined;\n\t},\n\trecordPoll: async (authReqId, polledAt) => {\n\t\tawait db\n\t\t\t.update(oauthBackchannelAuthRequestsTable)\n\t\t\t.set({ last_polled_at_ms: polledAt })\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t},\n\tsaveBackchannelAuth: async (request) => {\n\t\tawait db.insert(oauthBackchannelAuthRequestsTable).values({\n\t\t\tauth_req_id: request.authReqId,\n\t\t\tbinding_message: request.bindingMessage ?? null,\n\t\t\tclient_id: request.clientId,\n\t\t\tcreated_at_ms: request.createdAt,\n\t\t\texpires_at_ms: request.expiresAt,\n\t\t\tinterval_seconds: request.intervalSeconds,\n\t\t\tlast_polled_at_ms: request.lastPolledAt ?? null,\n\t\t\tscopes: request.scopes,\n\t\t\tstatus: request.status,\n\t\t\tuser_sub: request.userSub ?? null\n\t\t});\n\t},\n\tupdateStatus: async (authReqId, status, userSub) => {\n\t\tawait db\n\t\t\t.update(oauthBackchannelAuthRequestsTable)\n\t\t\t.set({ status, user_sub: userSub ?? null })\n\t\t\t.where(\n\t\t\t\teq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)\n\t\t\t);\n\t}\n});\n",
|
|
23
23
|
"import { and, desc, eq } from 'drizzle-orm';\nimport {\n\tbigint,\n\tjsonb,\n\tpgTable,\n\tprimaryKey,\n\tvarchar\n} from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type {\n\tInvitationState,\n\tMembershipStatus,\n\tOrganization,\n\tOrganizationInvitation,\n\tOrganizationMembership,\n\tOrganizationStore\n} from './types';\n\nconst ID_LENGTH = 255;\nconst NAME_LENGTH = 255;\nconst STATE_LENGTH = 16;\n\nexport const organizationInvitationsTable = pgTable(\n\t'auth_organization_invitations',\n\t{\n\t\taccepted_at_ms: bigint('accepted_at_ms', { mode: 'number' }),\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\temail: varchar('email', { length: ID_LENGTH }).notNull(),\n\t\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\t\tinvitation_id: varchar('invitation_id', {\n\t\t\tlength: ID_LENGTH\n\t\t}).primaryKey(),\n\t\tinviter_user_id: varchar('inviter_user_id', { length: ID_LENGTH }),\n\t\torganization_id: varchar('organization_id', {\n\t\t\tlength: ID_LENGTH\n\t\t}).notNull(),\n\t\troles: jsonb('roles').$type<string[]>().notNull().default([]),\n\t\tstate: varchar('state', { length: STATE_LENGTH })\n\t\t\t.$type<InvitationState>()\n\t\t\t.notNull()\n\t\t\t.default('pending'),\n\t\ttoken_hash: varchar('token_hash', { length: ID_LENGTH })\n\t\t\t.notNull()\n\t\t\t.unique()\n\t}\n);\nexport const organizationMembershipsTable = pgTable(\n\t'auth_organization_memberships',\n\t{\n\t\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\t\torganization_id: varchar('organization_id', {\n\t\t\tlength: ID_LENGTH\n\t\t}).notNull(),\n\t\troles: jsonb('roles').$type<string[]>().notNull().default([]),\n\t\tstatus: varchar('status', { length: STATE_LENGTH })\n\t\t\t.$type<MembershipStatus>()\n\t\t\t.notNull()\n\t\t\t.default('active'),\n\t\tupdated_at_ms: bigint('updated_at_ms', { mode: 'number' }).notNull(),\n\t\tuser_id: varchar('user_id', { length: ID_LENGTH }).notNull()\n\t},\n\t(table) => [primaryKey({ columns: [table.organization_id, table.user_id] })]\n);\nexport const organizationsTable = pgTable('auth_organizations', {\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tmetadata: jsonb('metadata').$type<Record<string, unknown>>(),\n\tname: varchar('name', { length: NAME_LENGTH }).notNull(),\n\torganization_id: varchar('organization_id', {\n\t\tlength: ID_LENGTH\n\t}).primaryKey(),\n\tupdated_at_ms: bigint('updated_at_ms', { mode: 'number' }).notNull()\n});\n\ntype OrganizationRow = typeof organizationsTable.$inferSelect;\ntype MembershipRow = typeof organizationMembershipsTable.$inferSelect;\ntype InvitationRow = typeof organizationInvitationsTable.$inferSelect;\ntype OrganizationInsert = typeof organizationsTable.$inferInsert;\ntype MembershipInsert = typeof organizationMembershipsTable.$inferInsert;\ntype InvitationInsert = typeof organizationInvitationsTable.$inferInsert;\n\nconst toOrganization = (row: OrganizationRow): Organization => ({\n\tcreatedAt: row.created_at_ms,\n\tmetadata: row.metadata ?? undefined,\n\tname: row.name,\n\torganizationId: row.organization_id,\n\tupdatedAt: row.updated_at_ms\n});\n\nconst toMembership = (row: MembershipRow): OrganizationMembership => ({\n\tcreatedAt: row.created_at_ms,\n\torganizationId: row.organization_id,\n\troles: row.roles,\n\tstatus: row.status,\n\tupdatedAt: row.updated_at_ms,\n\tuserId: row.user_id\n});\n\nconst toInvitation = (row: InvitationRow): OrganizationInvitation => ({\n\tacceptedAt: row.accepted_at_ms ?? undefined,\n\tcreatedAt: row.created_at_ms,\n\temail: row.email,\n\texpiresAt: row.expires_at_ms,\n\tinvitationId: row.invitation_id,\n\tinviterUserId: row.inviter_user_id ?? undefined,\n\torganizationId: row.organization_id,\n\troles: row.roles,\n\tstate: row.state,\n\ttokenHash: row.token_hash\n});\n\nexport const createNeonOrganizationStore = (databaseUrl: string) =>\n\tcreatePostgresOrganizationStore(createNeonDatabase(databaseUrl));\nexport const createPostgresOrganizationStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): OrganizationStore => ({\n\tdeleteOrganization: async (organizationId) => {\n\t\tawait db\n\t\t\t.delete(organizationsTable)\n\t\t\t.where(eq(organizationsTable.organization_id, organizationId));\n\t},\n\tgetInvitation: async (invitationId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(organizationInvitationsTable)\n\t\t\t.where(eq(organizationInvitationsTable.invitation_id, invitationId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toInvitation(row) : undefined;\n\t},\n\tgetInvitationByTokenHash: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(organizationInvitationsTable)\n\t\t\t.where(eq(organizationInvitationsTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\n\t\treturn row ? toInvitation(row) : undefined;\n\t},\n\tgetMembership: async (organizationId, userId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(organizationMembershipsTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(\n\t\t\t\t\t\torganizationMembershipsTable.organization_id,\n\t\t\t\t\t\torganizationId\n\t\t\t\t\t),\n\t\t\t\t\teq(organizationMembershipsTable.user_id, userId)\n\t\t\t\t)\n\t\t\t)\n\t\t\t.limit(1);\n\n\t\treturn row ? toMembership(row) : undefined;\n\t},\n\tgetOrganization: async (organizationId) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(organizationsTable)\n\t\t\t.where(eq(organizationsTable.organization_id, organizationId))\n\t\t\t.limit(1);\n\n\t\treturn row ? toOrganization(row) : undefined;\n\t},\n\tlistInvitationsByOrganization: async (organizationId) => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(organizationInvitationsTable)\n\t\t\t.where(\n\t\t\t\teq(organizationInvitationsTable.organization_id, organizationId)\n\t\t\t);\n\n\t\treturn rows.map(toInvitation);\n\t},\n\tlistMembershipsByOrganization: async (organizationId) => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(organizationMembershipsTable)\n\t\t\t.where(\n\t\t\t\teq(organizationMembershipsTable.organization_id, organizationId)\n\t\t\t);\n\n\t\treturn rows.map(toMembership);\n\t},\n\tlistMembershipsByUser: async (userId) => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(organizationMembershipsTable)\n\t\t\t.where(eq(organizationMembershipsTable.user_id, userId));\n\n\t\treturn rows.map(toMembership);\n\t},\n\tlistOrganizations: async () => {\n\t\tconst rows = await db\n\t\t\t.select()\n\t\t\t.from(organizationsTable)\n\t\t\t.orderBy(desc(organizationsTable.created_at_ms));\n\n\t\treturn rows.map(toOrganization);\n\t},\n\tremoveMembership: async (organizationId, userId) => {\n\t\tawait db\n\t\t\t.delete(organizationMembershipsTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(\n\t\t\t\t\t\torganizationMembershipsTable.organization_id,\n\t\t\t\t\t\torganizationId\n\t\t\t\t\t),\n\t\t\t\t\teq(organizationMembershipsTable.user_id, userId)\n\t\t\t\t)\n\t\t\t);\n\t},\n\tsaveInvitation: async (invitation) => {\n\t\tconst values: InvitationInsert = {\n\t\t\taccepted_at_ms: invitation.acceptedAt ?? null,\n\t\t\tcreated_at_ms: invitation.createdAt,\n\t\t\temail: invitation.email,\n\t\t\texpires_at_ms: invitation.expiresAt,\n\t\t\tinvitation_id: invitation.invitationId,\n\t\t\tinviter_user_id: invitation.inviterUserId ?? null,\n\t\t\torganization_id: invitation.organizationId,\n\t\t\troles: invitation.roles,\n\t\t\tstate: invitation.state,\n\t\t\ttoken_hash: invitation.tokenHash\n\t\t};\n\t\tawait db\n\t\t\t.insert(organizationInvitationsTable)\n\t\t\t.values(values)\n\t\t\t.onConflictDoUpdate({\n\t\t\t\tset: values,\n\t\t\t\ttarget: organizationInvitationsTable.invitation_id\n\t\t\t});\n\t},\n\tsaveMembership: async (membership) => {\n\t\tconst values: MembershipInsert = {\n\t\t\tcreated_at_ms: membership.createdAt,\n\t\t\torganization_id: membership.organizationId,\n\t\t\troles: membership.roles,\n\t\t\tstatus: membership.status,\n\t\t\tupdated_at_ms: membership.updatedAt,\n\t\t\tuser_id: membership.userId\n\t\t};\n\t\tawait db\n\t\t\t.insert(organizationMembershipsTable)\n\t\t\t.values(values)\n\t\t\t.onConflictDoUpdate({\n\t\t\t\tset: values,\n\t\t\t\ttarget: [\n\t\t\t\t\torganizationMembershipsTable.organization_id,\n\t\t\t\t\torganizationMembershipsTable.user_id\n\t\t\t\t]\n\t\t\t});\n\t},\n\tsaveOrganization: async (organization) => {\n\t\tconst values: OrganizationInsert = {\n\t\t\tcreated_at_ms: organization.createdAt,\n\t\t\tmetadata: organization.metadata ?? null,\n\t\t\tname: organization.name,\n\t\t\torganization_id: organization.organizationId,\n\t\t\tupdated_at_ms: organization.updatedAt\n\t\t};\n\t\tawait db.insert(organizationsTable).values(values).onConflictDoUpdate({\n\t\t\tset: values,\n\t\t\ttarget: organizationsTable.organization_id\n\t\t});\n\t}\n});\n",
|
|
24
24
|
"import { eq } from 'drizzle-orm';\nimport { bigint, pgTable, varchar } from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type { PasswordlessToken, PasswordlessTokenStore } from './types';\n\nconst ID_LENGTH = 255;\n\nexport const passwordlessTokensTable = pgTable('auth_passwordless_tokens', {\n\temail: varchar('email', { length: ID_LENGTH }).notNull(),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).primaryKey()\n});\n\ntype PasswordlessRow = typeof passwordlessTokensTable.$inferSelect;\ntype PasswordlessInsert = typeof passwordlessTokensTable.$inferInsert;\n\nconst toToken = (row: PasswordlessRow): PasswordlessToken => ({\n\temail: row.email,\n\texpiresAt: row.expires_at_ms,\n\ttokenHash: row.token_hash\n});\n\nexport const createNeonPasswordlessTokenStore = (databaseUrl: string) =>\n\tcreatePostgresPasswordlessTokenStore(createNeonDatabase(databaseUrl));\nexport const createPostgresPasswordlessTokenStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): PasswordlessTokenStore => ({\n\tconsumeToken: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.delete(passwordlessTokensTable)\n\t\t\t.where(eq(passwordlessTokensTable.token_hash, tokenHash))\n\t\t\t.returning();\n\n\t\treturn row ? toToken(row) : undefined;\n\t},\n\tsaveToken: async (token) => {\n\t\tconst values: PasswordlessInsert = {\n\t\t\temail: token.email,\n\t\t\texpires_at_ms: token.expiresAt,\n\t\t\ttoken_hash: token.tokenHash\n\t\t};\n\t\tawait db\n\t\t\t.insert(passwordlessTokensTable)\n\t\t\t.values(values)\n\t\t\t.onConflictDoUpdate({\n\t\t\t\tset: values,\n\t\t\t\ttarget: passwordlessTokensTable.token_hash\n\t\t\t});\n\t}\n});\n",
|
|
25
25
|
"import { eq } from 'drizzle-orm';\nimport { bigint, jsonb, pgTable, varchar } from 'drizzle-orm/pg-core';\nimport { type AnyPgDatabase, createNeonDatabase } from '../stores/postgres';\nimport type { SetupCapability, SetupSession, SetupSessionStore } from './types';\n\nconst ID_LENGTH = 255;\n\nexport const setupSessionsTable = pgTable('auth_setup_sessions', {\n\tcapabilities: jsonb('capabilities')\n\t\t.$type<SetupCapability[]>()\n\t\t.notNull()\n\t\t.default([]),\n\tcreated_at_ms: bigint('created_at_ms', { mode: 'number' }).notNull(),\n\tcreated_by: varchar('created_by', { length: ID_LENGTH }),\n\texpires_at_ms: bigint('expires_at_ms', { mode: 'number' }).notNull(),\n\torganization_id: varchar('organization_id', {\n\t\tlength: ID_LENGTH\n\t}).notNull(),\n\tsetup_session_id: varchar('setup_session_id', {\n\t\tlength: ID_LENGTH\n\t}).primaryKey(),\n\ttoken_hash: varchar('token_hash', { length: ID_LENGTH }).notNull().unique()\n});\n\ntype SetupRow = typeof setupSessionsTable.$inferSelect;\ntype SetupInsert = typeof setupSessionsTable.$inferInsert;\n\nconst toSession = (row: SetupRow): SetupSession => ({\n\tcapabilities: row.capabilities,\n\tcreatedAt: row.created_at_ms,\n\tcreatedBy: row.created_by ?? undefined,\n\texpiresAt: row.expires_at_ms,\n\torganizationId: row.organization_id,\n\tsetupSessionId: row.setup_session_id,\n\ttokenHash: row.token_hash\n});\n\nexport const createNeonSetupSessionStore = (databaseUrl: string) =>\n\tcreatePostgresSetupSessionStore(createNeonDatabase(databaseUrl));\nexport const createPostgresSetupSessionStore = <DB extends AnyPgDatabase>(\n\tdb: DB\n): SetupSessionStore => ({\n\tdeleteSetupSession: async (setupSessionId) => {\n\t\tawait db\n\t\t\t.delete(setupSessionsTable)\n\t\t\t.where(eq(setupSessionsTable.setup_session_id, setupSessionId));\n\t},\n\tgetSetupSessionByTokenHash: async (tokenHash) => {\n\t\tconst [row] = await db\n\t\t\t.select()\n\t\t\t.from(setupSessionsTable)\n\t\t\t.where(eq(setupSessionsTable.token_hash, tokenHash))\n\t\t\t.limit(1);\n\n\t\treturn row ? toSession(row) : undefined;\n\t},\n\tsaveSetupSession: async (session) => {\n\t\tconst values: SetupInsert = {\n\t\t\tcapabilities: session.capabilities,\n\t\t\tcreated_at_ms: session.createdAt,\n\t\t\tcreated_by: session.createdBy ?? null,\n\t\t\texpires_at_ms: session.expiresAt,\n\t\t\torganization_id: session.organizationId,\n\t\t\tsetup_session_id: session.setupSessionId,\n\t\t\ttoken_hash: session.tokenHash\n\t\t};\n\t\tawait db.insert(setupSessionsTable).values(values).onConflictDoUpdate({\n\t\t\tset: values,\n\t\t\ttarget: setupSessionsTable.setup_session_id\n\t\t});\n\t}\n});\n",
|
package/dist/index.js
CHANGED
|
@@ -27708,6 +27708,16 @@ var createInMemoryAuthorizationCodeStore = () => {
|
|
|
27708
27708
|
codes.delete(codeHash);
|
|
27709
27709
|
return record;
|
|
27710
27710
|
},
|
|
27711
|
+
deleteForClient: async (clientId) => {
|
|
27712
|
+
let deleted = 0;
|
|
27713
|
+
for (const [hash, code] of codes) {
|
|
27714
|
+
if (code.clientId !== clientId)
|
|
27715
|
+
continue;
|
|
27716
|
+
codes.delete(hash);
|
|
27717
|
+
deleted += 1;
|
|
27718
|
+
}
|
|
27719
|
+
return deleted;
|
|
27720
|
+
},
|
|
27711
27721
|
deleteForUserClient: async (userId, clientId) => {
|
|
27712
27722
|
let deleted = 0;
|
|
27713
27723
|
for (const [hash, code] of codes) {
|
|
@@ -27773,6 +27783,16 @@ var createInMemoryClientRegistrationTokenStore = () => {
|
|
|
27773
27783
|
byHash.delete(hash);
|
|
27774
27784
|
}
|
|
27775
27785
|
},
|
|
27786
|
+
deleteForClient: async (clientId) => {
|
|
27787
|
+
let deleted = 0;
|
|
27788
|
+
for (const [hash, token] of byHash) {
|
|
27789
|
+
if (token.clientId !== clientId)
|
|
27790
|
+
continue;
|
|
27791
|
+
byHash.delete(hash);
|
|
27792
|
+
deleted += 1;
|
|
27793
|
+
}
|
|
27794
|
+
return deleted;
|
|
27795
|
+
},
|
|
27776
27796
|
findByTokenHash: async (tokenHash) => byHash.get(tokenHash),
|
|
27777
27797
|
saveToken: async (token) => {
|
|
27778
27798
|
for (const [hash, existing] of byHash) {
|
|
@@ -27789,6 +27809,16 @@ var createInMemoryDeviceAuthorizationStore = () => {
|
|
|
27789
27809
|
deleteByDeviceCodeHash: async (deviceCodeHash) => {
|
|
27790
27810
|
byDeviceCode.delete(deviceCodeHash);
|
|
27791
27811
|
},
|
|
27812
|
+
deleteForClient: async (clientId) => {
|
|
27813
|
+
let deleted = 0;
|
|
27814
|
+
for (const [hash, authorization] of byDeviceCode) {
|
|
27815
|
+
if (authorization.clientId !== clientId)
|
|
27816
|
+
continue;
|
|
27817
|
+
byDeviceCode.delete(hash);
|
|
27818
|
+
deleted += 1;
|
|
27819
|
+
}
|
|
27820
|
+
return deleted;
|
|
27821
|
+
},
|
|
27792
27822
|
deleteForUserClient: async (userId, clientId) => {
|
|
27793
27823
|
let deleted = 0;
|
|
27794
27824
|
for (const [hash, authorization] of byDeviceCode) {
|
|
@@ -27870,6 +27900,16 @@ var createInMemoryOidcRefreshTokenStore = () => {
|
|
|
27870
27900
|
tokens.delete(tokenHash);
|
|
27871
27901
|
return record;
|
|
27872
27902
|
},
|
|
27903
|
+
deleteForClient: async (clientId) => {
|
|
27904
|
+
let deleted = 0;
|
|
27905
|
+
for (const [hash, token] of tokens) {
|
|
27906
|
+
if (token.clientId !== clientId)
|
|
27907
|
+
continue;
|
|
27908
|
+
tokens.delete(hash);
|
|
27909
|
+
deleted += 1;
|
|
27910
|
+
}
|
|
27911
|
+
return deleted;
|
|
27912
|
+
},
|
|
27873
27913
|
deleteForUser: async (userId) => {
|
|
27874
27914
|
for (const [hash, token] of tokens) {
|
|
27875
27915
|
if (token.userId === userId)
|
|
@@ -28164,6 +28204,10 @@ var createPostgresAuthorizationCodeStore = (db) => ({
|
|
|
28164
28204
|
const [row] = await db.delete(oauthCodesTable).where(eq(oauthCodesTable.code_hash, codeHash)).returning();
|
|
28165
28205
|
return row ? toCode(row) : undefined;
|
|
28166
28206
|
},
|
|
28207
|
+
deleteForClient: async (clientId) => {
|
|
28208
|
+
const deleted = await db.delete(oauthCodesTable).where(eq(oauthCodesTable.client_id, clientId)).returning({ codeHash: oauthCodesTable.code_hash });
|
|
28209
|
+
return deleted.length;
|
|
28210
|
+
},
|
|
28167
28211
|
deleteForUserClient: async (userId, clientId) => {
|
|
28168
28212
|
const deleted = await db.delete(oauthCodesTable).where(and(eq(oauthCodesTable.user_id, userId), eq(oauthCodesTable.client_id, clientId))).returning({ codeHash: oauthCodesTable.code_hash });
|
|
28169
28213
|
return deleted.length;
|
|
@@ -28193,6 +28237,12 @@ var createPostgresClientRegistrationTokenStore = (db) => ({
|
|
|
28193
28237
|
deleteByClientId: async (clientId) => {
|
|
28194
28238
|
await db.delete(oauthClientRegistrationTokensTable).where(eq(oauthClientRegistrationTokensTable.client_id, clientId));
|
|
28195
28239
|
},
|
|
28240
|
+
deleteForClient: async (clientId) => {
|
|
28241
|
+
const deleted = await db.delete(oauthClientRegistrationTokensTable).where(eq(oauthClientRegistrationTokensTable.client_id, clientId)).returning({
|
|
28242
|
+
tokenHash: oauthClientRegistrationTokensTable.token_hash
|
|
28243
|
+
});
|
|
28244
|
+
return deleted.length;
|
|
28245
|
+
},
|
|
28196
28246
|
findByTokenHash: async (tokenHash) => {
|
|
28197
28247
|
const [row] = await db.select().from(oauthClientRegistrationTokensTable).where(eq(oauthClientRegistrationTokensTable.token_hash, tokenHash)).limit(1);
|
|
28198
28248
|
if (!row)
|
|
@@ -28217,6 +28267,12 @@ var createPostgresDeviceAuthorizationStore = (db) => ({
|
|
|
28217
28267
|
deleteByDeviceCodeHash: async (deviceCodeHash) => {
|
|
28218
28268
|
await db.delete(oauthDeviceAuthorizationsTable).where(eq(oauthDeviceAuthorizationsTable.device_code_hash, deviceCodeHash));
|
|
28219
28269
|
},
|
|
28270
|
+
deleteForClient: async (clientId) => {
|
|
28271
|
+
const deleted = await db.delete(oauthDeviceAuthorizationsTable).where(eq(oauthDeviceAuthorizationsTable.client_id, clientId)).returning({
|
|
28272
|
+
deviceCodeHash: oauthDeviceAuthorizationsTable.device_code_hash
|
|
28273
|
+
});
|
|
28274
|
+
return deleted.length;
|
|
28275
|
+
},
|
|
28220
28276
|
deleteForUserClient: async (userId, clientId) => {
|
|
28221
28277
|
const deleted = await db.delete(oauthDeviceAuthorizationsTable).where(and(eq(oauthDeviceAuthorizationsTable.user_sub, userId), eq(oauthDeviceAuthorizationsTable.client_id, clientId))).returning({
|
|
28222
28278
|
deviceCodeHash: oauthDeviceAuthorizationsTable.device_code_hash
|
|
@@ -28311,6 +28367,10 @@ var createPostgresOidcRefreshTokenStore = (db) => ({
|
|
|
28311
28367
|
const [row] = await db.delete(oauthRefreshTokensTable).where(eq(oauthRefreshTokensTable.token_hash, tokenHash)).returning();
|
|
28312
28368
|
return row ? toRefresh(row) : undefined;
|
|
28313
28369
|
},
|
|
28370
|
+
deleteForClient: async (clientId) => {
|
|
28371
|
+
const deleted = await db.delete(oauthRefreshTokensTable).where(eq(oauthRefreshTokensTable.client_id, clientId)).returning({ tokenHash: oauthRefreshTokensTable.token_hash });
|
|
28372
|
+
return deleted.length;
|
|
28373
|
+
},
|
|
28314
28374
|
deleteForUser: async (userId) => {
|
|
28315
28375
|
await db.delete(oauthRefreshTokensTable).where(eq(oauthRefreshTokensTable.user_id, userId));
|
|
28316
28376
|
},
|
|
@@ -30996,5 +31056,5 @@ export {
|
|
|
30996
31056
|
AGENT_CLAIM_GRANT_TYPE
|
|
30997
31057
|
};
|
|
30998
31058
|
|
|
30999
|
-
//# debugId=
|
|
31059
|
+
//# debugId=A84E5E023D14ADF464756E2164756E21
|
|
31000
31060
|
//# sourceMappingURL=index.js.map
|