@atproto/oauth-provider 0.13.2 → 0.13.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atproto/oauth-provider
2
2
 
3
+ ## 0.13.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4293](https://github.com/bluesky-social/atproto/pull/4293) [`8c03d75b6`](https://github.com/bluesky-social/atproto/commit/8c03d75b6c11bed15b58bfa7ff4bf68199fc6511) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove un-implemented `introspect` endpoint from OAuth Authorization Server metadata
8
+
9
+ - [#4265](https://github.com/bluesky-social/atproto/pull/4265) [`1e702ea67`](https://github.com/bluesky-social/atproto/commit/1e702ea675e3697e050be1f28e54bb1298b56436) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `onResetPasswordRequested` and `onResetPasswordConfirmed` hooks to be called after the respective actions are completed.
10
+
11
+ - Updated dependencies [[`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58), [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58), [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58), [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58)]:
12
+ - @atproto/oauth-types@0.5.0
13
+ - @atproto-labs/fetch-node@0.2.0
14
+ - @atproto/oauth-provider-api@0.3.2
15
+ - @atproto/oauth-provider-frontend@0.2.3
16
+ - @atproto/oauth-provider-ui@0.3.3
17
+ - @atproto/lexicon-resolver@0.2.3
18
+
3
19
  ## 0.13.2
4
20
 
5
21
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.d.ts","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account-manager.d.ts","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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"}
@@ -156,7 +156,16 @@ class AccountManager {
156
156
  deviceMetadata,
157
157
  });
158
158
  return (0, time_js_1.constantTime)(TIMING_ATTACK_MITIGATION_DELAY, async () => {
159
- await this.store.resetPasswordRequest(input);
159
+ const account = await this.store.resetPasswordRequest(input);
160
+ if (!account) {
161
+ return; // Silently ignore to prevent user enumeration
162
+ }
163
+ await this.hooks.onResetPasswordRequested?.call(null, {
164
+ input,
165
+ deviceId,
166
+ deviceMetadata,
167
+ account,
168
+ });
160
169
  });
161
170
  }
162
171
  async resetPasswordConfirm(deviceId, deviceMetadata, input) {
@@ -166,7 +175,16 @@ class AccountManager {
166
175
  deviceMetadata,
167
176
  });
168
177
  return (0, time_js_1.constantTime)(TIMING_ATTACK_MITIGATION_DELAY, async () => {
169
- await this.store.resetPasswordConfirm(input);
178
+ const account = await this.store.resetPasswordConfirm(input);
179
+ if (!account) {
180
+ throw new invalid_request_error_js_1.InvalidRequestError('Invalid token');
181
+ }
182
+ await this.hooks.onResetPasswordConfirmed?.call(null, {
183
+ input,
184
+ deviceId,
185
+ deviceMetadata,
186
+ account,
187
+ });
170
188
  });
171
189
  }
172
190
  async verifyHandleAvailability(handle) {
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.js","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n OAuthIssuerIdentifier,\n isOAuthClientIdLoopback,\n} from '@atproto/oauth-types'\nimport { Client } from '../client/client.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { HCaptchaClient, HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { constantTime } from '../lib/util/time.js'\nimport { OAuthHooks, RequestMetadata } from '../oauth-hooks.js'\nimport { Customization } from '../oauth-provider.js'\nimport { Sub } from '../oidc/sub.js'\nimport {\n Account,\n AccountStore,\n AuthorizedClientData,\n DeviceAccount,\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account-store.js'\nimport { SignInData } from './sign-in-data.js'\nimport { SignUpInput } from './sign-up-input.js'\n\nconst TIMING_ATTACK_MITIGATION_DELAY = 400\nconst BRUTE_FORCE_MITIGATION_DELAY = 300\n\nexport class AccountManager {\n protected readonly inviteCodeRequired: boolean\n protected readonly hcaptchaClient?: HCaptchaClient\n\n constructor(\n issuer: OAuthIssuerIdentifier,\n protected readonly store: AccountStore,\n protected readonly hooks: OAuthHooks,\n customization: Customization,\n ) {\n this.inviteCodeRequired = customization.inviteCodeRequired !== false\n this.hcaptchaClient = customization.hcaptcha\n ? new HCaptchaClient(new URL(issuer).hostname, customization.hcaptcha)\n : undefined\n }\n\n protected async processHcaptchaToken(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<HcaptchaVerifyResult | undefined> {\n if (!this.hcaptchaClient) {\n return undefined\n }\n\n if (!input.hcaptchaToken) {\n throw new InvalidRequestError('hCaptcha token is required')\n }\n\n const tokens = this.hcaptchaClient.buildClientTokens(\n deviceMetadata.ipAddress,\n input.handle,\n deviceMetadata.userAgent,\n )\n\n const result = await this.hcaptchaClient\n .verify('signup', input.hcaptchaToken, deviceMetadata.ipAddress, tokens)\n .catch((err) => {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n })\n\n await this.hooks.onHcaptchaResult?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n tokens,\n result,\n })\n\n try {\n this.hcaptchaClient.checkVerifyResult(result, tokens)\n } catch (err) {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n }\n\n return result\n }\n\n protected async enforceInviteCode(\n input: SignUpInput,\n _deviceId: DeviceId,\n _deviceMetadata: RequestMetadata,\n ): Promise<string | undefined> {\n if (!this.inviteCodeRequired) {\n return undefined\n }\n\n if (!input.inviteCode) {\n throw new InvalidRequestError('Invite code is required')\n }\n\n return input.inviteCode\n }\n\n protected async buildSignupData(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<SignUpData> {\n const [hcaptchaResult, inviteCode] = await Promise.all([\n this.processHcaptchaToken(input, deviceId, deviceMetadata),\n this.enforceInviteCode(input, deviceId, deviceMetadata),\n ])\n\n return { ...input, hcaptchaResult, inviteCode }\n }\n\n public async createAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: SignUpInput,\n ): Promise<Account> {\n await this.hooks.onSignUpAttempt?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n const data = await this.buildSignupData(input, deviceId, deviceMetadata)\n\n // Mitigation against brute forcing email of users.\n // @TODO Add rate limit to all the OAuth routes.\n const account = await constantTime(\n BRUTE_FORCE_MITIGATION_DELAY,\n async () => {\n return this.store.createAccount(data)\n },\n ).catch((err) => {\n throw InvalidRequestError.from(err, 'Account creation failed')\n })\n\n try {\n await this.hooks.onSignedUp?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n await this.removeDeviceAccount(deviceId, account.sub)\n\n throw InvalidRequestError.from(\n err,\n 'The account was successfully created but something went wrong, try signing-in.',\n )\n }\n }\n\n public async authenticateAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n data: SignInData,\n ): Promise<Account> {\n try {\n await this.hooks.onSignInAttempt?.call(null, {\n data,\n deviceId,\n deviceMetadata,\n })\n\n const account = await constantTime(\n TIMING_ATTACK_MITIGATION_DELAY,\n async () => {\n return this.store.authenticateAccount(data)\n },\n )\n\n await this.hooks.onSignedIn?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n throw InvalidRequestError.from(\n err,\n 'Unable to sign-in due to an unexpected server error',\n )\n }\n }\n\n public async upsertDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<void> {\n await this.store.upsertDeviceAccount(deviceId, sub)\n }\n\n public async getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<DeviceAccount> {\n const deviceAccount = await this.store.getDeviceAccount(deviceId, sub)\n if (!deviceAccount) throw new InvalidRequestError(`Account not found`)\n\n return deviceAccount\n }\n\n public async setAuthorizedClient(\n account: Account,\n client: Client,\n data: AuthorizedClientData,\n ): Promise<void> {\n // \"Loopback\" clients are not distinguishable from one another.\n if (isOAuthClientIdLoopback(client.id)) return\n\n await this.store.setAuthorizedClient(account.sub, client.id, data)\n }\n\n public async getAccount(sub: Sub) {\n return this.store.getAccount(sub)\n }\n\n public async removeDeviceAccount(deviceId: DeviceId, sub: Sub) {\n return this.store.removeDeviceAccount(deviceId, sub)\n }\n\n public async listDeviceAccounts(\n deviceId: DeviceId,\n ): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n deviceId,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.deviceId === deviceId)\n }\n\n public async listAccountDevices(sub: Sub): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n sub,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.account.sub === sub)\n }\n\n public async resetPasswordRequest(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordRequestInput,\n ) {\n await this.hooks.onResetPasswordRequest?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n await this.store.resetPasswordRequest(input)\n })\n }\n\n public async resetPasswordConfirm(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordConfirmInput,\n ) {\n await this.hooks.onResetPasswordConfirm?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n await this.store.resetPasswordConfirm(input)\n })\n }\n\n public async verifyHandleAvailability(handle: string): Promise<void> {\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n return this.store.verifyHandleAvailability(handle)\n })\n }\n}\n"]}
1
+ {"version":3,"file":"account-manager.js","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n OAuthIssuerIdentifier,\n isOAuthClientIdLoopback,\n} from '@atproto/oauth-types'\nimport { Client } from '../client/client.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { HCaptchaClient, HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { constantTime } from '../lib/util/time.js'\nimport { OAuthHooks, RequestMetadata } from '../oauth-hooks.js'\nimport { Customization } from '../oauth-provider.js'\nimport { Sub } from '../oidc/sub.js'\nimport {\n Account,\n AccountStore,\n AuthorizedClientData,\n DeviceAccount,\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account-store.js'\nimport { SignInData } from './sign-in-data.js'\nimport { SignUpInput } from './sign-up-input.js'\n\nconst TIMING_ATTACK_MITIGATION_DELAY = 400\nconst BRUTE_FORCE_MITIGATION_DELAY = 300\n\nexport class AccountManager {\n protected readonly inviteCodeRequired: boolean\n protected readonly hcaptchaClient?: HCaptchaClient\n\n constructor(\n issuer: OAuthIssuerIdentifier,\n protected readonly store: AccountStore,\n protected readonly hooks: OAuthHooks,\n customization: Customization,\n ) {\n this.inviteCodeRequired = customization.inviteCodeRequired !== false\n this.hcaptchaClient = customization.hcaptcha\n ? new HCaptchaClient(new URL(issuer).hostname, customization.hcaptcha)\n : undefined\n }\n\n protected async processHcaptchaToken(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<HcaptchaVerifyResult | undefined> {\n if (!this.hcaptchaClient) {\n return undefined\n }\n\n if (!input.hcaptchaToken) {\n throw new InvalidRequestError('hCaptcha token is required')\n }\n\n const tokens = this.hcaptchaClient.buildClientTokens(\n deviceMetadata.ipAddress,\n input.handle,\n deviceMetadata.userAgent,\n )\n\n const result = await this.hcaptchaClient\n .verify('signup', input.hcaptchaToken, deviceMetadata.ipAddress, tokens)\n .catch((err) => {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n })\n\n await this.hooks.onHcaptchaResult?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n tokens,\n result,\n })\n\n try {\n this.hcaptchaClient.checkVerifyResult(result, tokens)\n } catch (err) {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n }\n\n return result\n }\n\n protected async enforceInviteCode(\n input: SignUpInput,\n _deviceId: DeviceId,\n _deviceMetadata: RequestMetadata,\n ): Promise<string | undefined> {\n if (!this.inviteCodeRequired) {\n return undefined\n }\n\n if (!input.inviteCode) {\n throw new InvalidRequestError('Invite code is required')\n }\n\n return input.inviteCode\n }\n\n protected async buildSignupData(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<SignUpData> {\n const [hcaptchaResult, inviteCode] = await Promise.all([\n this.processHcaptchaToken(input, deviceId, deviceMetadata),\n this.enforceInviteCode(input, deviceId, deviceMetadata),\n ])\n\n return { ...input, hcaptchaResult, inviteCode }\n }\n\n public async createAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: SignUpInput,\n ): Promise<Account> {\n await this.hooks.onSignUpAttempt?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n const data = await this.buildSignupData(input, deviceId, deviceMetadata)\n\n // Mitigation against brute forcing email of users.\n // @TODO Add rate limit to all the OAuth routes.\n const account = await constantTime(\n BRUTE_FORCE_MITIGATION_DELAY,\n async () => {\n return this.store.createAccount(data)\n },\n ).catch((err) => {\n throw InvalidRequestError.from(err, 'Account creation failed')\n })\n\n try {\n await this.hooks.onSignedUp?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n await this.removeDeviceAccount(deviceId, account.sub)\n\n throw InvalidRequestError.from(\n err,\n 'The account was successfully created but something went wrong, try signing-in.',\n )\n }\n }\n\n public async authenticateAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n data: SignInData,\n ): Promise<Account> {\n try {\n await this.hooks.onSignInAttempt?.call(null, {\n data,\n deviceId,\n deviceMetadata,\n })\n\n const account = await constantTime(\n TIMING_ATTACK_MITIGATION_DELAY,\n async () => {\n return this.store.authenticateAccount(data)\n },\n )\n\n await this.hooks.onSignedIn?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n throw InvalidRequestError.from(\n err,\n 'Unable to sign-in due to an unexpected server error',\n )\n }\n }\n\n public async upsertDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<void> {\n await this.store.upsertDeviceAccount(deviceId, sub)\n }\n\n public async getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<DeviceAccount> {\n const deviceAccount = await this.store.getDeviceAccount(deviceId, sub)\n if (!deviceAccount) throw new InvalidRequestError(`Account not found`)\n\n return deviceAccount\n }\n\n public async setAuthorizedClient(\n account: Account,\n client: Client,\n data: AuthorizedClientData,\n ): Promise<void> {\n // \"Loopback\" clients are not distinguishable from one another.\n if (isOAuthClientIdLoopback(client.id)) return\n\n await this.store.setAuthorizedClient(account.sub, client.id, data)\n }\n\n public async getAccount(sub: Sub) {\n return this.store.getAccount(sub)\n }\n\n public async removeDeviceAccount(deviceId: DeviceId, sub: Sub) {\n return this.store.removeDeviceAccount(deviceId, sub)\n }\n\n public async listDeviceAccounts(\n deviceId: DeviceId,\n ): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n deviceId,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.deviceId === deviceId)\n }\n\n public async listAccountDevices(sub: Sub): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n sub,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.account.sub === sub)\n }\n\n public async resetPasswordRequest(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordRequestInput,\n ) {\n await this.hooks.onResetPasswordRequest?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordRequest(input)\n\n if (!account) {\n return // Silently ignore to prevent user enumeration\n }\n\n await this.hooks.onResetPasswordRequested?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async resetPasswordConfirm(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordConfirmInput,\n ) {\n await this.hooks.onResetPasswordConfirm?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordConfirm(input)\n\n if (!account) {\n throw new InvalidRequestError('Invalid token')\n }\n\n await this.hooks.onResetPasswordConfirmed?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async verifyHandleAvailability(handle: string): Promise<void> {\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n return this.store.verifyHandleAvailability(handle)\n })\n }\n}\n"]}
@@ -125,8 +125,8 @@ export interface AccountStore {
125
125
  } | {
126
126
  deviceId: DeviceId;
127
127
  }): Awaitable<DeviceAccount[]>;
128
- resetPasswordRequest(data: ResetPasswordRequestInput): Awaitable<void>;
129
- resetPasswordConfirm(data: ResetPasswordConfirmInput): Awaitable<void>;
128
+ resetPasswordRequest(data: ResetPasswordRequestInput): Awaitable<null | Account>;
129
+ resetPasswordConfirm(data: ResetPasswordConfirmInput): Awaitable<null | Account>;
130
130
  /**
131
131
  * @throws {HandleUnavailableError} - To indicate that the handle is already taken
132
132
  */
@@ -1 +1 @@
1
- {"version":3,"file":"account-store.d.ts","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account-store.d.ts","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"account-store.js","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAoMA,wCAKC;AA/LD,iDAAsE;AACtE,wDAI2B;AAsBzB,uGAzBA,wCAAsB,OAyBA;AACtB,oGAzBA,qCAAmB,OAyBA;AACnB,wHAzBA,yDAAuC,OAyBA;AAnBzC,kEAAkE;AAElE,yDAAsC;AACtC,2DAAwC;AACxC,yDAAsC;AACtC,iDAA8B;AAC9B,2DAAwC;AA4J3B,QAAA,cAAc,GAAG,IAAA,+BAAqB,EAAe;IAChE,eAAe;IACf,qBAAqB;IACrB,qBAAqB;IACrB,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC,CAAA;AAEF,SAAgB,cAAc,CAAI,cAAiB;IACjD,IAAI,CAAC,cAAc,IAAI,CAAC,IAAA,sBAAc,EAAC,cAAc,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import {\n Account,\n ConfirmResetPasswordInput,\n InitiatePasswordResetInput,\n} from '@atproto/oauth-provider-api'\nimport { OAuthScope } from '@atproto/oauth-types'\nimport { ClientId } from '../client/client-id.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { DeviceData } from '../device/device-store.js'\nimport { HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'\nimport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n} from '../oauth-errors.js'\nimport { Sub } from '../oidc/sub.js'\nimport { InviteCode } from '../types/invite-code.js'\nimport { SignUpInput } from './sign-up-input.js'\n\n// Export all types needed to implement the AccountStore interface\n\nexport * from '../client/client-id.js'\nexport * from '../device/device-data.js'\nexport * from '../device/device-id.js'\nexport * from '../oidc/sub.js'\nexport * from '../request/request-id.js'\n\nexport type {\n Account,\n HcaptchaVerifyResult,\n InviteCode,\n OAuthScope,\n SignUpInput,\n}\n\nexport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n}\n\nexport type ResetPasswordRequestInput = InitiatePasswordResetInput\nexport type ResetPasswordConfirmInput = ConfirmResetPasswordInput\n\nexport type CreateAccountData = {\n locale: string\n email: string\n password: string\n handle: string\n inviteCode?: string | undefined\n}\n\nexport type AuthenticateAccountData = {\n locale: string\n password: string\n username: string\n emailOtp?: string | undefined\n}\n\nexport type AuthorizedClientData = { authorizedScopes: readonly string[] }\nexport type AuthorizedClients = Map<ClientId, AuthorizedClientData>\n\nexport type DeviceAccount = {\n deviceId: DeviceId\n\n /**\n * The data associated with the device, created through the\n * {@link DeviceStore}. This data is used to identify devices on which a user\n * has logged in.\n */\n deviceData: DeviceData\n\n /**\n * The account associated with the device account.\n */\n account: Account\n\n /**\n * The list of clients that are authorized by the account, as created through\n * the {@link AccountStore.setAuthorizedClient} method.\n */\n authorizedClients: AuthorizedClients\n\n /**\n * The date at which the device account was created. This value is currently\n * not used.\n */\n createdAt: Date\n\n /**\n * The date at which the device account was last updated. This value is used\n * to determine the date at which the user last authenticated on a device\n */\n updatedAt: Date\n}\n\nexport type SignUpData = SignUpInput & {\n hcaptchaResult?: HcaptchaVerifyResult\n inviteCode?: InviteCode\n}\n\nexport interface AccountStore {\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n * @throws {InvalidRequestError} - To indicate that some data is invalid\n */\n createAccount(data: CreateAccountData): Awaitable<Account>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n * @throws {SecondAuthenticationFactorRequiredError} - To indicate that an {@link SecondAuthenticationFactorRequiredError.type} is required in the credentials\n */\n authenticateAccount(data: AuthenticateAccountData): Awaitable<Account>\n\n /**\n * Add a client & scopes to the list of authorized clients for the given account.\n */\n setAuthorizedClient(\n sub: Sub,\n clientId: ClientId,\n data: AuthorizedClientData,\n ): Awaitable<void>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n */\n getAccount(sub: Sub): Awaitable<{\n account: Account\n authorizedClients: AuthorizedClients\n }>\n\n /**\n * @param data.requestId - If provided, the inserted account must be bound to\n * that particular requestId.\n *\n * @note Whenever a particular device account is created, all **unbound**\n * device accounts for the same `deviceId` & `sub` should be deleted.\n *\n * @note When a particular request is deleted (through\n * {@link RequestStore.deleteRequest}), all accounts bound to that request\n * should be deleted as well.\n */\n upsertDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @param requestId - If provided, the result must either have the same\n * requestId, or not be bound to a particular requestId. If `null`, the\n * result must not be bound to a particular requestId.\n * @throws {InvalidRequestError} - Instead of returning `null` in order to\n * provide a custom error message\n */\n getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Awaitable<DeviceAccount | null>\n\n /**\n * Removes *all* the unbound device-accounts associated with the given device\n * & account.\n *\n * @note Noop if the device-account is not found.\n */\n removeDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @returns **all** the device accounts that match the {@link requestId}\n * criteria and given {@link filter}.\n */\n listDeviceAccounts(\n filter: { sub: Sub } | { deviceId: DeviceId },\n ): Awaitable<DeviceAccount[]>\n\n resetPasswordRequest(data: ResetPasswordRequestInput): Awaitable<void>\n resetPasswordConfirm(data: ResetPasswordConfirmInput): Awaitable<void>\n\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n */\n verifyHandleAvailability(handle: string): Awaitable<void>\n}\n\nexport const isAccountStore = buildInterfaceChecker<AccountStore>([\n 'createAccount',\n 'authenticateAccount',\n 'setAuthorizedClient',\n 'getAccount',\n 'upsertDeviceAccount',\n 'getDeviceAccount',\n 'removeDeviceAccount',\n 'listDeviceAccounts',\n 'resetPasswordRequest',\n 'resetPasswordConfirm',\n 'verifyHandleAvailability',\n])\n\nexport function asAccountStore<V>(implementation: V): V & AccountStore {\n if (!implementation || !isAccountStore(implementation)) {\n throw new Error('Invalid AccountStore implementation')\n }\n return implementation\n}\n"]}
1
+ {"version":3,"file":"account-store.js","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAyMA,wCAKC;AApMD,iDAAsE;AACtE,wDAI2B;AAsBzB,uGAzBA,wCAAsB,OAyBA;AACtB,oGAzBA,qCAAmB,OAyBA;AACnB,wHAzBA,yDAAuC,OAyBA;AAnBzC,kEAAkE;AAElE,yDAAsC;AACtC,2DAAwC;AACxC,yDAAsC;AACtC,iDAA8B;AAC9B,2DAAwC;AAiK3B,QAAA,cAAc,GAAG,IAAA,+BAAqB,EAAe;IAChE,eAAe;IACf,qBAAqB;IACrB,qBAAqB;IACrB,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC,CAAA;AAEF,SAAgB,cAAc,CAAI,cAAiB;IACjD,IAAI,CAAC,cAAc,IAAI,CAAC,IAAA,sBAAc,EAAC,cAAc,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import {\n Account,\n ConfirmResetPasswordInput,\n InitiatePasswordResetInput,\n} from '@atproto/oauth-provider-api'\nimport { OAuthScope } from '@atproto/oauth-types'\nimport { ClientId } from '../client/client-id.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { DeviceData } from '../device/device-store.js'\nimport { HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'\nimport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n} from '../oauth-errors.js'\nimport { Sub } from '../oidc/sub.js'\nimport { InviteCode } from '../types/invite-code.js'\nimport { SignUpInput } from './sign-up-input.js'\n\n// Export all types needed to implement the AccountStore interface\n\nexport * from '../client/client-id.js'\nexport * from '../device/device-data.js'\nexport * from '../device/device-id.js'\nexport * from '../oidc/sub.js'\nexport * from '../request/request-id.js'\n\nexport type {\n Account,\n HcaptchaVerifyResult,\n InviteCode,\n OAuthScope,\n SignUpInput,\n}\n\nexport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n}\n\nexport type ResetPasswordRequestInput = InitiatePasswordResetInput\nexport type ResetPasswordConfirmInput = ConfirmResetPasswordInput\n\nexport type CreateAccountData = {\n locale: string\n email: string\n password: string\n handle: string\n inviteCode?: string | undefined\n}\n\nexport type AuthenticateAccountData = {\n locale: string\n password: string\n username: string\n emailOtp?: string | undefined\n}\n\nexport type AuthorizedClientData = { authorizedScopes: readonly string[] }\nexport type AuthorizedClients = Map<ClientId, AuthorizedClientData>\n\nexport type DeviceAccount = {\n deviceId: DeviceId\n\n /**\n * The data associated with the device, created through the\n * {@link DeviceStore}. This data is used to identify devices on which a user\n * has logged in.\n */\n deviceData: DeviceData\n\n /**\n * The account associated with the device account.\n */\n account: Account\n\n /**\n * The list of clients that are authorized by the account, as created through\n * the {@link AccountStore.setAuthorizedClient} method.\n */\n authorizedClients: AuthorizedClients\n\n /**\n * The date at which the device account was created. This value is currently\n * not used.\n */\n createdAt: Date\n\n /**\n * The date at which the device account was last updated. This value is used\n * to determine the date at which the user last authenticated on a device\n */\n updatedAt: Date\n}\n\nexport type SignUpData = SignUpInput & {\n hcaptchaResult?: HcaptchaVerifyResult\n inviteCode?: InviteCode\n}\n\nexport interface AccountStore {\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n * @throws {InvalidRequestError} - To indicate that some data is invalid\n */\n createAccount(data: CreateAccountData): Awaitable<Account>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n * @throws {SecondAuthenticationFactorRequiredError} - To indicate that an {@link SecondAuthenticationFactorRequiredError.type} is required in the credentials\n */\n authenticateAccount(data: AuthenticateAccountData): Awaitable<Account>\n\n /**\n * Add a client & scopes to the list of authorized clients for the given account.\n */\n setAuthorizedClient(\n sub: Sub,\n clientId: ClientId,\n data: AuthorizedClientData,\n ): Awaitable<void>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n */\n getAccount(sub: Sub): Awaitable<{\n account: Account\n authorizedClients: AuthorizedClients\n }>\n\n /**\n * @param data.requestId - If provided, the inserted account must be bound to\n * that particular requestId.\n *\n * @note Whenever a particular device account is created, all **unbound**\n * device accounts for the same `deviceId` & `sub` should be deleted.\n *\n * @note When a particular request is deleted (through\n * {@link RequestStore.deleteRequest}), all accounts bound to that request\n * should be deleted as well.\n */\n upsertDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @param requestId - If provided, the result must either have the same\n * requestId, or not be bound to a particular requestId. If `null`, the\n * result must not be bound to a particular requestId.\n * @throws {InvalidRequestError} - Instead of returning `null` in order to\n * provide a custom error message\n */\n getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Awaitable<DeviceAccount | null>\n\n /**\n * Removes *all* the unbound device-accounts associated with the given device\n * & account.\n *\n * @note Noop if the device-account is not found.\n */\n removeDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @returns **all** the device accounts that match the {@link requestId}\n * criteria and given {@link filter}.\n */\n listDeviceAccounts(\n filter: { sub: Sub } | { deviceId: DeviceId },\n ): Awaitable<DeviceAccount[]>\n\n resetPasswordRequest(\n data: ResetPasswordRequestInput,\n ): Awaitable<null | Account>\n\n resetPasswordConfirm(\n data: ResetPasswordConfirmInput,\n ): Awaitable<null | Account>\n\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n */\n verifyHandleAvailability(handle: string): Awaitable<void>\n}\n\nexport const isAccountStore = buildInterfaceChecker<AccountStore>([\n 'createAccount',\n 'authenticateAccount',\n 'setAuthorizedClient',\n 'getAccount',\n 'upsertDeviceAccount',\n 'getDeviceAccount',\n 'removeDeviceAccount',\n 'listDeviceAccounts',\n 'resetPasswordRequest',\n 'resetPasswordConfirm',\n 'verifyHandleAvailability',\n])\n\nexport function asAccountStore<V>(implementation: V): V & AccountStore {\n if (!implementation || !isAccountStore(implementation)) {\n throw new Error('Invalid AccountStore implementation')\n }\n return implementation\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client-manager.d.ts","sourceRoot":"","sources":["../../src/client/client-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAiB,MAAM,cAAc,CAAA;AAC1D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EAKzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,EAKN,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EACL,YAAY,EAEZ,WAAW,EACZ,MAAM,4BAA4B,CAAA;AAInC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAepC,MAAM,MAAM,sBAAsB,GAAG,CACnC,GAAG,EAAE,MAAM,KACR,SAAS,CAAC,wBAAwB,CAAC,CAAA;AAExC,qBAAa,aAAa;IAKtB,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,gCAAgC;IACnE,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACjC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU;IACpC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAC5C,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,IAAI;IARpE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACnD,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;gBAGvD,cAAc,EAAE,gCAAgC,EAChD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,gBAAgB,GAAE,sBAAsB,GAAG,IAAI,aAAO,EACzE,SAAS,EAAE,KAAK,EAChB,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,EAC1C,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAsB/D;;;OAGG;IACU,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAiC5B,WAAW,CACtB,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC7B,EACE,OAEC,GACF,GAAE;QACD,OAAO,CAAC,EAAE,CACR,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,QAAQ,KACf,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;KACrC,GACL,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;cAoBjB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC;cAYf,yBAAyB,CACvC,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,mBAAmB,CAAC;cA2Bf,6BAA6B,CAC3C,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,mBAAmB,CAAC;cAYf,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC;IAS/B;;;;;OAKG;IACH,SAAS,CAAC,sBAAsB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;IA8btB,8BAA8B,CAC5B,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;IAuCtB,kCAAkC,CAChC,QAAQ,EAAE,yBAAyB,EACnC,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;CAmEvB"}
1
+ {"version":3,"file":"client-manager.d.ts","sourceRoot":"","sources":["../../src/client/client-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAiB,MAAM,cAAc,CAAA;AAC1D,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EAKzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,EAKN,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,YAAY,EAEZ,WAAW,EACZ,MAAM,4BAA4B,CAAA;AAInC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAepC,MAAM,MAAM,sBAAsB,GAAG,CACnC,GAAG,EAAE,MAAM,KACR,SAAS,CAAC,wBAAwB,CAAC,CAAA;AAExC,qBAAa,aAAa;IAKtB,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,gCAAgC;IACnE,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACjC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU;IACpC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAC5C,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,IAAI;IARpE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACnD,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;gBAGvD,cAAc,EAAE,gCAAgC,EAChD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,gBAAgB,GAAE,sBAAsB,GAAG,IAAI,aAAO,EACzE,SAAS,EAAE,KAAK,EAChB,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,EAC1C,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAsB/D;;;OAGG;IACU,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAiC5B,WAAW,CACtB,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC7B,EACE,OAEC,GACF,GAAE;QACD,OAAO,CAAC,EAAE,CACR,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,QAAQ,KACf,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;KACrC,GACL,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;cAoBjB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC;cAYf,yBAAyB,CACvC,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,mBAAmB,CAAC;cA2Bf,6BAA6B,CAC3C,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,mBAAmB,CAAC;cAYf,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC;IAS/B;;;;;OAKG;IACH,SAAS,CAAC,sBAAsB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;IAsZtB,8BAA8B,CAC5B,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;IAuBtB,kCAAkC,CAChC,QAAQ,EAAE,yBAAyB,EACnC,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB;CAgFvB"}
@@ -4,7 +4,6 @@ exports.ClientManager = void 0;
4
4
  const jwk_1 = require("@atproto/jwk");
5
5
  const oauth_types_1 = require("@atproto/oauth-types");
6
6
  const fetch_1 = require("@atproto-labs/fetch");
7
- const fetch_node_1 = require("@atproto-labs/fetch-node");
8
7
  const pipe_1 = require("@atproto-labs/pipe");
9
8
  const simple_store_1 = require("@atproto-labs/simple-store");
10
9
  const invalid_client_metadata_error_js_1 = require("../errors/invalid-client-metadata-error.js");
@@ -13,7 +12,7 @@ const function_js_1 = require("../lib/util/function.js");
13
12
  const client_utils_js_1 = require("./client-utils.js");
14
13
  const client_js_1 = require("./client.js");
15
14
  const fetchMetadataHandler = (0, pipe_1.pipe)((0, fetch_1.fetchOkProcessor)(),
16
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1
15
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1
17
16
  (0, fetch_1.fetchJsonProcessor)('application/json', true), (0, fetch_1.fetchJsonZodProcessor)(oauth_types_1.oauthClientMetadataSchema));
18
17
  const fetchJwksHandler = (0, pipe_1.pipe)((0, fetch_1.fetchOkProcessor)(), (0, fetch_1.fetchJsonProcessor)('application/json', false), (0, fetch_1.fetchJsonZodProcessor)(jwk_1.jwksPubSchema));
19
18
  class ClientManager {
@@ -126,6 +125,9 @@ class ClientManager {
126
125
  * requirements.
127
126
  */
128
127
  validateClientMetadata(clientId, metadata) {
128
+ // @TODO This method should only check for rules that are specific to this
129
+ // implementation or the ATPROTO specification. All generic validation rules
130
+ // should be moved to the @atproto/oauth-types package.
129
131
  if (metadata.jwks && metadata.jwks_uri) {
130
132
  throw new invalid_client_metadata_error_js_1.InvalidClientMetadataError('jwks_uri and jwks are mutually exclusive');
131
133
  }
@@ -143,7 +145,7 @@ class ClientManager {
143
145
  const clientUriUrl = metadata.client_uri
144
146
  ? new URL(metadata.client_uri)
145
147
  : null;
146
- if (clientUriUrl && (0, fetch_node_1.isLocalHostname)(clientUriUrl.hostname)) {
148
+ if (clientUriUrl && (0, oauth_types_1.isLocalHostname)(clientUriUrl.hostname)) {
147
149
  throw new invalid_client_metadata_error_js_1.InvalidClientMetadataError('client_uri hostname is invalid');
148
150
  }
149
151
  const scopes = metadata.scope?.split(' ');
@@ -350,7 +352,7 @@ class ClientManager {
350
352
  throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError('Only loopback redirect URIs are allowed to use the "http" scheme');
351
353
  }
352
354
  case url.protocol === 'https:': {
353
- if ((0, fetch_node_1.isLocalHostname)(url.hostname)) {
355
+ if ((0, oauth_types_1.isLocalHostname)(url.hostname)) {
354
356
  throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Redirect URI "${url}"'s domain name must not be a local hostname`);
355
357
  }
356
358
  // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
@@ -386,42 +388,9 @@ class ClientManager {
386
388
  break;
387
389
  }
388
390
  case isPrivateUseUriScheme(url): {
389
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
390
- //
391
- // > When choosing a URI scheme to associate with the app, apps MUST
392
- // > use a URI scheme based on a domain name under their control,
393
- // > expressed in reverse order, as recommended by Section 3.8 of
394
- // > [RFC7595] for private-use URI schemes.
395
391
  if (metadata.application_type !== 'native') {
396
392
  throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Private-Use URI Scheme redirect URI are only allowed for native apps`);
397
393
  }
398
- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
399
- //
400
- // > In addition to the collision-resistant properties, requiring a
401
- // > URI scheme based on a domain name that is under the control of
402
- // > the app can help to prove ownership in the event of a dispute
403
- // > where two apps claim the same private-use URI scheme (where one
404
- // > app is acting maliciously).
405
- //
406
- // We can't check for ownership here (as there is no concept of
407
- // proven ownership in the generic client validation), but we can
408
- // check that the domain is a valid domain name.
409
- const urlDomain = reverseDomain(url.protocol.slice(0, -1));
410
- if ((0, fetch_node_1.isLocalHostname)(urlDomain)) {
411
- throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Private-use URI Scheme redirect URI must not be a local hostname`);
412
- }
413
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
414
- //
415
- // > Following the requirements of Section 3.2 of [RFC3986], as there
416
- // > is no naming authority for private-use URI scheme redirects, only
417
- // > a single slash ("/") appears after the scheme component.
418
- if (url.href.startsWith(`${url.protocol}//`) ||
419
- url.username ||
420
- url.password ||
421
- url.hostname ||
422
- url.port) {
423
- throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Private-Use URI Scheme must be in the form ${url.protocol}/<path>`);
424
- }
425
394
  break;
426
395
  }
427
396
  default:
@@ -453,25 +422,16 @@ class ClientManager {
453
422
  if (method !== 'none') {
454
423
  throw new invalid_client_metadata_error_js_1.InvalidClientMetadataError(`Loopback clients are not allowed to use "token_endpoint_auth_method" ${method}`);
455
424
  }
456
- for (const redirectUri of metadata.redirect_uris) {
457
- const url = (0, client_utils_js_1.parseRedirectUri)(redirectUri);
458
- if (url.protocol !== 'http:') {
459
- throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Loopback clients must use HTTP redirect URIs`);
460
- }
461
- if (!(0, oauth_types_1.isLoopbackHost)(url.hostname)) {
462
- throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Loopback clients must use loopback redirect URIs`);
463
- }
464
- }
465
425
  return metadata;
466
426
  }
467
427
  validateDiscoverableClientMetadata(clientId, metadata) {
468
428
  if (!metadata.client_id) {
469
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
429
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
470
430
  throw new invalid_client_metadata_error_js_1.InvalidClientMetadataError(`client_id is required for discoverable clients`);
471
431
  }
472
432
  const clientIdUrl = (0, client_utils_js_1.parseDiscoverableClientId)(clientId);
473
433
  if (metadata.client_uri) {
474
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
434
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
475
435
  //
476
436
  // The client_uri must be a parent of the client_id URL. This might be
477
437
  // relaxed in the future.
@@ -488,8 +448,16 @@ class ClientManager {
488
448
  }
489
449
  }
490
450
  for (const redirectUri of metadata.redirect_uris) {
451
+ // @NOTE at this point, all redirect URIs have already been validated by
452
+ // oauthRedirectUriSchema
491
453
  const url = (0, client_utils_js_1.parseRedirectUri)(redirectUri);
492
454
  if (isPrivateUseUriScheme(url)) {
455
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
456
+ //
457
+ // > When choosing a URI scheme to associate with the app, apps MUST use
458
+ // > a URI scheme based on a domain name under their control, expressed
459
+ // > in reverse order, as recommended by Section 3.8 of [RFC7595] for
460
+ // > private-use URI schemes.
493
461
  // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
494
462
  //
495
463
  // > In addition to the collision-resistant properties, requiring a
@@ -497,11 +465,14 @@ class ClientManager {
497
465
  // > the app can help to prove ownership in the event of a dispute
498
466
  // > where two apps claim the same private-use URI scheme (where one
499
467
  // > app is acting maliciously).
500
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
468
+ // https://atproto.com/specs/oauth
501
469
  //
502
- // Fully qualified domain name (FQDN) of the client_id, in reverse
503
- // order. This could be relaxed to allow same apex domain names, or
504
- // parent domains, but for now we require an exact match.
470
+ // > Any custom scheme must match the client_id hostname in
471
+ // > reverse-domain order. The URI scheme must be followed by a single
472
+ // > colon (:) then a single forward slash (/) and then a URI path
473
+ // > component. For example, an app with client_id
474
+ // > https://app.example.com/client-metadata.json could have a
475
+ // > redirect_uri of com.example.app:/callback.
505
476
  const protocol = `${reverseDomain(clientIdUrl.hostname)}:`;
506
477
  if (url.protocol !== protocol) {
507
478
  throw new invalid_redirect_uri_error_js_1.InvalidRedirectUriError(`Private-Use URI Scheme redirect URI, for discoverable client metadata, must be the fully qualified domain name (FQDN) of the client_id, in reverse order (${protocol})`);
@@ -1 +1 @@
1
- {"version":3,"file":"client-manager.js","sourceRoot":"","sources":["../../src/client/client-manager.ts"],"names":[],"mappings":";;;AAAA,sCAA0D;AAC1D,sDAU6B;AAC7B,+CAM4B;AAC5B,yDAA0D;AAC1D,6CAAyC;AACzC,6DAImC;AACnC,iGAAuF;AACvF,2FAAiF;AACjF,yDAAmD;AAKnD,uDAA+E;AAC/E,2CAAoC;AAEpC,MAAM,oBAAoB,GAAG,IAAA,WAAI,EAC/B,IAAA,wBAAgB,GAAE;AAClB,8IAA8I;AAC9I,IAAA,0BAAkB,EAAC,kBAAkB,EAAE,IAAI,CAAC,EAC5C,IAAA,6BAAqB,EAAC,uCAAyB,CAAC,CACjD,CAAA;AAED,MAAM,gBAAgB,GAAG,IAAA,WAAI,EAC3B,IAAA,wBAAgB,GAAE,EAClB,IAAA,0BAAkB,EAAC,kBAAkB,EAAE,KAAK,CAAC,EAC7C,IAAA,6BAAqB,EAAC,mBAAa,CAAC,CACrC,CAAA;AAMD,MAAa,aAAa;IAKH;IACA;IACA;IACA;IACA;IARF,IAAI,CAA4B;IAChC,cAAc,CAA2C;IAE5E,YACqB,cAAgD,EAChD,MAAc,EACd,KAAiB,EACjB,KAAyB,EACzB,mBAAkD,IAAI,EACzE,SAAgB,EAChB,eAA0C,EAC1C,mBAA6D;QAP1C,mBAAc,GAAd,cAAc,CAAkC;QAChD,WAAM,GAAN,MAAM,CAAQ;QACd,UAAK,GAAL,KAAK,CAAY;QACjB,UAAK,GAAL,KAAK,CAAoB;QACzB,qBAAgB,GAAhB,gBAAgB,CAAsC;QAKzE,MAAM,KAAK,GAAG,IAAA,iBAAS,EAAC,SAAS,CAAC,CAAA;QAElC,IAAI,CAAC,IAAI,GAAG,IAAI,2BAAY,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAClD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAC9D,gBAAgB,CACjB,CAAA;YAED,OAAO,IAAI,CAAA;QACb,CAAC,EAAE,eAAe,CAAC,CAAA;QAEnB,IAAI,CAAC,cAAc,GAAG,IAAI,2BAAY,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAC5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAClE,oBAAoB,CACrB,CAAA;YAED,+DAA+D;YAC/D,OAAO,IAAI,CAAC,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QACnD,CAAC,EAAE,mBAAmB,CAAC,CAAA;IACzB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,SAAS,CAAC,QAAkB;QACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACpE,MAAM,6DAA0B,CAAC,IAAI,CACnC,GAAG,EACH,yCAAyC,QAAQ,GAAG,CACrD,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ;YAC5B,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACnD,MAAM,6DAA0B,CAAC,IAAI,CACnC,GAAG,EACH,+BAA+B,QAAQ,CAAC,QAAQ,UAAU,QAAQ,GAAG,CACtE,CAAA;YACH,CAAC,CAAC;YACJ,CAAC,CAAC,SAAS,CAAA;QAEb,MAAM,WAAW,GAAG,MAAM,IAAA,uBAAS,EAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,QAAQ,EAAE;YACtE,QAAQ;YACR,IAAI;SACL,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACf,MAAM,6DAA0B,CAAC,IAAI,CACnC,GAAG,EACH,oCAAoC,QAAQ,GAAG,CAChD,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,YAAY,GAAG,WAAW,EAAE,YAAY,IAAI,KAAK,CAAA;QACvD,MAAM,SAAS,GAAG,WAAW,EAAE,SAAS,IAAI,YAAY,CAAA;QAExD,OAAO,IAAI,kBAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAA;IAC1E,CAAC;IAEM,KAAK,CAAC,WAAW,CACtB,SAA6B,EAC7B,EACE,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;QAChB,MAAM,GAAG,CAAA;IACX,CAAC,MAMC,EAAE;QAEN,yDAAyD;QACzD,MAAM,eAAe,GACnB,SAAS,YAAY,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;QAE3D,wCAAwC;QACxC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAC7C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAChE,CACF,CAAA;QAED,gCAAgC;QAChC,OAAO,IAAI,GAAG,CACZ,OAAO;aACJ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,kBAAM,CAAC;aAC/C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CACzB,CAAA;IACH,CAAC;IAES,KAAK,CAAC,iBAAiB,CAC/B,QAAkB;QAElB,IAAI,IAAA,qCAAuB,EAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAA;QACjD,CAAC;aAAM,IAAI,IAAA,yCAA2B,EAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC,6BAA6B,CAAC,QAAQ,CAAC,CAAA;QACrD,CAAC;aAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAA;QAC/C,CAAC;QAED,MAAM,IAAI,6DAA0B,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAA;IACzE,CAAC;IAES,KAAK,CAAC,yBAAyB,CACvC,QAA+B;QAE/B,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAA;QACjC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,6DAA0B,CAAC,kCAAkC,CAAC,CAAA;QAC1E,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,IAAA,uBAAS,EAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,KAAK,CACnE,CAAC,GAAG,EAAE,EAAE;YACN,MAAM,6DAA0B,CAAC,IAAI,CACnC,GAAG,EACH,+BAA+B,QAAQ,GAAG,CAC3C,CAAA;QACH,CAAC,CACF,CAAA;QAED,MAAM,QAAQ,GAAG,MAAM,uCAAyB;aAC7C,UAAU,CAAC,WAAW,CAAC;aACvB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,MAAM,6DAA0B,CAAC,IAAI,CACnC,GAAG,EACH,yCAAyC,QAAQ,GAAG,CACrD,CAAA;QACH,CAAC,CAAC,CAAA;QAEJ,OAAO,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IACxD,CAAC;IAES,KAAK,CAAC,6BAA6B,CAC3C,QAAmC;QAEnC,MAAM,WAAW,GAAG,IAAA,2CAAyB,EAAC,QAAQ,CAAC,CAAA;QAEvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAEhE,sEAAsE;QACtE,mEAAmE;QACnE,EAAE;QACF,iEAAiE;QACjE,OAAO,QAAQ,CAAA;IACjB,CAAC;IAES,KAAK,CAAC,uBAAuB,CACrC,QAAkB;QAElB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;YACtD,OAAO,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACxD,CAAC;QAED,MAAM,IAAI,6DAA0B,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAA;IACzE,CAAC;IAED;;;;;OAKG;IACO,sBAAsB,CAC9B,QAAkB,EAClB,QAA6B;QAE7B,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,6DAA0B,CAClC,0CAA0C,CAC3C,CAAA;QACH,CAAC;QAED,iCAAiC;QACjC,KAAK,MAAM,CAAC,IAAI;YACd,iBAAiB;YACjB,8BAA8B;YAC9B,8BAA8B;YAC9B,iCAAiC;SACzB,EAAE,CAAC;YACX,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;gBACxB,MAAM,IAAI,6DAA0B,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAA;YACtE,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,UAAU;YACtC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC9B,CAAC,CAAC,IAAI,CAAA;QAER,IAAI,YAAY,IAAI,IAAA,4BAAe,EAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,6DAA0B,CAAC,gCAAgC,CAAC,CAAA;QACxE,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;QAEzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,6DAA0B,CAAC,wBAAwB,CAAC,CAAA;QAChE,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,6DAA0B,CAAC,yBAAyB,CAAC,CAAA;QACjE,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAA;QAC1C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,6DAA0B,CAAC,oBAAoB,QAAQ,GAAG,CAAC,CAAA;QACvE,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC3D,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,IAAI,6DAA0B,CAClC,yBAAyB,YAAY,GAAG,CACzC,CAAA;QACH,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC7C,QAAQ,SAAS,EAAE,CAAC;gBAClB,KAAK,UAAU;oBACb,yBAAyB;oBACzB,MAAM,IAAI,6DAA0B,CAClC,eAAe,SAAS,kBAAkB,CAC3C,CAAA;gBAEH,kDAAkD;gBAClD,6BAA6B;gBAC7B,mBAAmB;gBACnB,KAAK,oBAAoB,CAAC;gBAC1B,KAAK,eAAe;oBAClB,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,qBAAqB,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;wBACpE,MAAM,IAAI,6DAA0B,CAClC,2BAA2B,SAAS,GAAG,CACxC,CAAA;oBACH,CAAC;oBACD,MAAK;gBAEP;oBACE,MAAM,IAAI,6DAA0B,CAClC,eAAe,SAAS,oBAAoB,CAC7C,CAAA;YACL,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC1D,MAAM,IAAI,6DAA0B,CAAC,0BAA0B,CAAC,CAAA;QAClE,CAAC;QAED,IAAI,QAAQ,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAChE,MAAM,IAAI,6DAA0B,CAClC,yCAAyC,CAC1C,CAAA;QACH,CAAC;QAED,QAAQ,QAAQ,CAAC,0BAA0B,EAAE,CAAC;YAC5C,KAAK,MAAM;gBACT,IAAI,QAAQ,CAAC,+BAA+B,EAAE,CAAC;oBAC7C,MAAM,IAAI,6DAA0B,CAClC,iFAAiF,CAClF,CAAA;gBACH,CAAC;gBACD,MAAK;YAEP,KAAK,iBAAiB;gBACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;oBACzC,MAAM,IAAI,6DAA0B,CAClC,uDAAuD,CACxD,CAAA;gBACH,CAAC;gBACD,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACrC,MAAM,IAAI,6DAA0B,CAClC,+DAA+D,CAChE,CAAA;gBACH,CAAC;gBACD,IAAI,CAAC,QAAQ,CAAC,+BAA+B,EAAE,CAAC;oBAC9C,MAAM,IAAI,6DAA0B,CAClC,yDAAyD,CAC1D,CAAA;gBACH,CAAC;gBACD,MAAK;YAEP;gBACE,MAAM,IAAI,6DAA0B,CAClC,6CAA6C,QAAQ,CAAC,0BAA0B,gEAAgE,kBAAM,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAC9L,CAAA;QACL,CAAC;QAED,IAAI,QAAQ,CAAC,oCAAoC,EAAE,CAAC;YAClD,MAAM,IAAI,6DAA0B,CAClC,mDAAmD,CACpD,CAAA;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,0CAA0C,EAAE,CAAC;YACxD,MAAM,IAAI,6DAA0B,CAClC,kDAAkD,CACnD,CAAA;QACH,CAAC;QAED,IACE,QAAQ,CAAC,oCAAoC;YAC7C,CAAC,QAAQ,CAAC,oCAAoC,EAC9C,CAAC;YACD,MAAM,IAAI,6DAA0B,CAClC,oFAAoF,CACrF,CAAA;QACH,CAAC;QAED,uEAAuE;QACvE,IAAI,QAAQ,CAAC,wBAAwB,KAAK,IAAI,EAAE,CAAC;YAC/C,MAAM,IAAI,6DAA0B,CAClC,yCAAyC,CAC1C,CAAA;QACH,CAAC;QAED,+DAA+D;QAC/D,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,6DAA0B,CAAC,oCAAoC,CAAC,CAAA;QAC5E,CAAC;aAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAChE,oBAAoB;YACpB,MAAM,IAAI,6DAA0B,CAClC,oFAAoF,CACrF,CAAA;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,2BAA2B,EAAE,MAAM,EAAE,CAAC;YACjD,MAAM,kBAAkB,GACtB,QAAQ,CAAC,2BAA2B,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACxD,IAAI,kBAAkB,EAAE,CAAC;gBACvB,MAAM,IAAI,6DAA0B,CAClC,yCAAyC,kBAAkB,GAAG,CAC/D,CAAA;YACH,CAAC;YAED,MAAM,kCAAkC,GACtC,IAAI,CAAC,cAAc,CAAC,qCAAqC,CAAA;YAC3D,IAAI,CAAC,kCAAkC,EAAE,CAAC;gBACxC,MAAM,IAAI,6DAA0B,CAClC,+CAA+C,CAChD,CAAA;YACH,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,2BAA2B,EAAE,CAAC;gBACxD,IAAI,CAAC,kCAAkC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACvD,MAAM,IAAI,6DAA0B,CAClC,2CAA2C,IAAI,GAAG,CACnD,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;YACpC,mEAAmE;YAEnE,MAAM,IAAI,6DAA0B,CAClC,uCAAuC,CACxC,CAAA;QACH,CAAC;QAED,IACE,QAAQ,CAAC,gBAAgB,KAAK,QAAQ;YACtC,QAAQ,CAAC,0BAA0B,KAAK,MAAM,EAC9C,CAAC;YACD,4DAA4D;YAC5D,EAAE;YACF,mEAAmE;YACnE,iEAAiE;YACjE,yEAAyE;YACzE,wEAAwE;YACxE,0EAA0E;YAC1E,mEAAmE;YACnE,iBAAiB;YAEjB,0EAA0E;YAC1E,yFAAyF;YACzF,eAAe;YAEf,MAAM,IAAI,6DAA0B,CAClC,sDAAsD,CACvD,CAAA;QACH,CAAC;QAED,IACE,QAAQ,CAAC,gBAAgB,KAAK,KAAK;YACnC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EACzC,CAAC;YACD,8EAA8E;YAC9E,EAAE;YACF,mEAAmE;YACnE,gEAAgE;YAChE,gEAAgE;YAChE,cAAc;YAEd,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;gBACjD,MAAM,GAAG,GAAG,IAAA,kCAAgB,EAAC,WAAW,CAAC,CAAA;gBACzC,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBAC9B,MAAM,IAAI,uDAAuB,CAC/B,0CAA0C,CAC3C,CAAA;gBACH,CAAC;gBAED,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;oBACjC,MAAM,IAAI,uDAAuB,CAC/B,oDAAoD,CACrD,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;YACjD,MAAM,GAAG,GAAG,IAAA,kCAAgB,EAAC,WAAW,CAAC,CAAA;YAEzC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;gBACjC,mEAAmE;gBACnE,MAAM,IAAI,uDAAuB,CAC/B,gBAAgB,GAAG,+BAA+B,CACnD,CAAA;YACH,CAAC;YAED,QAAQ,IAAI,EAAE,CAAC;gBACb,gEAAgE;gBAEhE,KAAK,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC;oBAClC,4DAA4D;oBAC5D,EAAE;oBACF,+CAA+C;oBAC/C,wEAAwE;oBACxE,oEAAoE;oBACpE,wEAAwE;oBACxE,oEAAoE;oBACpE,kEAAkE;oBAClE,qEAAqE;oBACrE,qCAAqC;oBACrC,MAAM,IAAI,uDAAuB,CAC/B,yBAAyB,GAAG,4CAA4C,CACzE,CAAA;gBACH,CAAC;gBAED,KAAK,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC;gBAClC,KAAK,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC;oBAC9B,+BAA+B;oBAC/B,IAAI,QAAQ,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;wBAC3C,MAAM,IAAI,uDAAuB,CAC/B,yDAAyD,CAC1D,CAAA;oBACH,CAAC;oBAED,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;wBACb,4DAA4D;wBAC5D,EAAE;wBACF,oEAAoE;wBACpE,8DAA8D;wBAC9D,gEAAgE;wBAChE,0DAA0D;wBAC1D,EAAE;wBACF,gEAAgE;wBAChE,+DAA+D;wBAC/D,+DAA+D;wBAC/D,oDAAoD;wBACpD,0BAA0B;oBAC5B,CAAC;oBAED,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;wBAC7B,4DAA4D;wBAC5D,EAAE;wBACF,qEAAqE;wBACrE,iEAAiE;wBACjE,sEAAsE;wBACtE,+CAA+C;wBAC/C,MAAM,IAAI,uDAAuB,CAC/B,yBAAyB,GAAG,gBAAgB,CAC7C,CAAA;oBACH,CAAC;oBAED,MAAK;gBACP,CAAC;gBAED,yCAAyC;gBAEzC,KAAK,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC;oBAC9B,8EAA8E;oBAC9E,EAAE;oBACF,gEAAgE;oBAChE,mEAAmE;oBACnE,YAAY;oBACZ,EAAE;oBACF,iEAAiE;oBACjE,mCAAmC;oBAEnC,8EAA8E;oBAC9E,EAAE;oBACF,kEAAkE;oBAClE,6DAA6D;oBAC7D,aAAa;oBACb,MAAM,IAAI,uDAAuB,CAC/B,kEAAkE,CACnE,CAAA;gBACH,CAAC;gBAED,KAAK,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC;oBAC/B,IAAI,IAAA,4BAAe,EAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAClC,MAAM,IAAI,uDAAuB,CAC/B,iBAAiB,GAAG,8CAA8C,CACnE,CAAA;oBACH,CAAC;oBAED,4DAA4D;oBAC5D,EAAE;oBACF,mEAAmE;oBACnE,mEAAmE;oBACnE,kEAAkE;oBAClE,oEAAoE;oBACpE,gCAAgC;oBAChC,EAAE;oBACF,oEAAoE;oBACpE,uDAAuD;oBACvD,EAAE;oBACF,qEAAqE;oBACrE,iEAAiE;oBACjE,gCAAgC;oBAEhC,oEAAoE;oBACpE,kEAAkE;oBAClE,yBAAyB;oBACzB,EAAE;oBACF,8EAA8E;oBAC9E,EAAE;oBACF,gEAAgE;oBAChE,qEAAqE;oBACrE,iEAAiE;oBACjE,0DAA0D;oBAC1D,EAAE;oBACF,gDAAgD;oBAChD,uCAAuC;oBACvC,qEAAqE;oBACrE,MAAM;oBACN,IAAI;oBAEJ,MAAK;gBACP,CAAC;gBAED,KAAK,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBAChC,4DAA4D;oBAC5D,EAAE;oBACF,oEAAoE;oBACpE,iEAAiE;oBACjE,iEAAiE;oBACjE,2CAA2C;oBAE3C,IAAI,QAAQ,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;wBAC3C,MAAM,IAAI,uDAAuB,CAC/B,sEAAsE,CACvE,CAAA;oBACH,CAAC;oBAED,4DAA4D;oBAC5D,EAAE;oBACF,mEAAmE;oBACnE,mEAAmE;oBACnE,kEAAkE;oBAClE,oEAAoE;oBACpE,gCAAgC;oBAChC,EAAE;oBACF,+DAA+D;oBAC/D,iEAAiE;oBACjE,gDAAgD;oBAEhD,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;oBAE1D,IAAI,IAAA,4BAAe,EAAC,SAAS,CAAC,EAAE,CAAC;wBAC/B,MAAM,IAAI,uDAAuB,CAC/B,kEAAkE,CACnE,CAAA;oBACH,CAAC;oBAED,4DAA4D;oBAC5D,EAAE;oBACF,qEAAqE;oBACrE,sEAAsE;oBACtE,6DAA6D;oBAC7D,IACE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,QAAQ,IAAI,CAAC;wBACxC,GAAG,CAAC,QAAQ;wBACZ,GAAG,CAAC,QAAQ;wBACZ,GAAG,CAAC,QAAQ;wBACZ,GAAG,CAAC,IAAI,EACR,CAAC;wBACD,MAAM,IAAI,uDAAuB,CAC/B,8CAA8C,GAAG,CAAC,QAAQ,SAAS,CACpE,CAAA;oBACH,CAAC;oBAED,MAAK;gBACP,CAAC;gBAED;oBACE,4DAA4D;oBAC5D,EAAE;oBACF,oEAAoE;oBACpE,+CAA+C;oBAC/C,MAAM,IAAI,uDAAuB,CAC/B,gCAAgC,GAAG,CAAC,QAAQ,GAAG,CAChD,CAAA;YACL,CAAC;QACH,CAAC;QAED,IAAI,IAAA,qCAAuB,EAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,8BAA8B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChE,CAAC;aAAM,IAAI,IAAA,yCAA2B,EAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC,kCAAkC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QACpE,CAAC;aAAM,CAAC;YACN,OAAO,QAAQ,CAAA;QACjB,CAAC;IACH,CAAC;IAED,8BAA8B,CAC5B,QAA+B,EAC/B,QAA6B;QAE7B,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxB,MAAM,IAAI,6DAA0B,CAClC,gDAAgD,CACjD,CAAA;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,IAAI,6DAA0B,CAClC,sDAAsD,CACvD,CAAA;QACH,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,0BAA0B,CAAA;QAClD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,6DAA0B,CAClC,wEAAwE,MAAM,EAAE,CACjF,CAAA;QACH,CAAC;QAED,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;YACjD,MAAM,GAAG,GAAG,IAAA,kCAAgB,EAAC,WAAW,CAAC,CAAA;YAEzC,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,uDAAuB,CAC/B,8CAA8C,CAC/C,CAAA;YACH,CAAC;YAED,IAAI,CAAC,IAAA,4BAAc,EAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,uDAAuB,CAC/B,kDAAkD,CACnD,CAAA;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,kCAAkC,CAChC,QAAmC,EACnC,QAA6B;QAE7B,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;YACxB,kIAAkI;YAClI,MAAM,IAAI,6DAA0B,CAClC,gDAAgD,CACjD,CAAA;QACH,CAAC;QAED,MAAM,WAAW,GAAG,IAAA,2CAAyB,EAAC,QAAQ,CAAC,CAAA;QAEvD,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxB,kIAAkI;YAClI,EAAE;YACF,sEAAsE;YACtE,yBAAyB;YAEzB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;YAEjD,IAAI,YAAY,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;gBAC/C,MAAM,IAAI,6DAA0B,CAClC,uDAAuD,CACxD,CAAA;YACH,CAAC;YAED,IAAI,WAAW,CAAC,QAAQ,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC;gBACnD,IACE,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAC9B,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;oBACjC,CAAC,CAAC,YAAY,CAAC,QAAQ;oBACvB,CAAC,CAAC,GAAG,YAAY,CAAC,QAAQ,GAAG,CAChC,EACD,CAAC;oBACD,MAAM,IAAI,6DAA0B,CAClC,kDAAkD,CACnD,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;YACjD,MAAM,GAAG,GAAG,IAAA,kCAAgB,EAAC,WAAW,CAAC,CAAA;YAEzC,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,4DAA4D;gBAC5D,EAAE;gBACF,mEAAmE;gBACnE,mEAAmE;gBACnE,kEAAkE;gBAClE,oEAAoE;gBACpE,gCAAgC;gBAEhC,kIAAkI;gBAClI,EAAE;gBACF,kEAAkE;gBAClE,mEAAmE;gBACnE,yDAAyD;gBACzD,MAAM,QAAQ,GAAG,GAAG,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAA;gBAC1D,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBAC9B,MAAM,IAAI,uDAAuB,CAC/B,6JAA6J,QAAQ,GAAG,CACzK,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAluBD,sCAkuBC;AAED,SAAS,WAAW,CAElB,KAAQ,EAAE,KAAa,EAAE,KAAU;IACnC,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;AACzC,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAQ;IACrC,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW,EAAE,OAA0B;IAClE,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE;QACtB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;QACvC,mDAAmD;QACnD,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QAChD,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Jwks, Keyset, jwksPubSchema } from '@atproto/jwk'\nimport {\n OAuthAuthorizationServerMetadata,\n OAuthClientIdDiscoverable,\n OAuthClientIdLoopback,\n OAuthClientMetadata,\n OAuthClientMetadataInput,\n isLoopbackHost,\n isOAuthClientIdDiscoverable,\n isOAuthClientIdLoopback,\n oauthClientMetadataSchema,\n} from '@atproto/oauth-types'\nimport {\n Fetch,\n bindFetch,\n fetchJsonProcessor,\n fetchJsonZodProcessor,\n fetchOkProcessor,\n} from '@atproto-labs/fetch'\nimport { isLocalHostname } from '@atproto-labs/fetch-node'\nimport { pipe } from '@atproto-labs/pipe'\nimport {\n CachedGetter,\n GetCachedOptions,\n SimpleStore,\n} from '@atproto-labs/simple-store'\nimport { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js'\nimport { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'\nimport { callAsync } from '../lib/util/function.js'\nimport { Awaitable } from '../lib/util/type.js'\nimport { OAuthHooks } from '../oauth-hooks.js'\nimport { ClientId } from './client-id.js'\nimport { ClientStore } from './client-store.js'\nimport { parseDiscoverableClientId, parseRedirectUri } from './client-utils.js'\nimport { Client } from './client.js'\n\nconst fetchMetadataHandler = pipe(\n fetchOkProcessor(),\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1\n fetchJsonProcessor('application/json', true),\n fetchJsonZodProcessor(oauthClientMetadataSchema),\n)\n\nconst fetchJwksHandler = pipe(\n fetchOkProcessor(),\n fetchJsonProcessor('application/json', false),\n fetchJsonZodProcessor(jwksPubSchema),\n)\n\nexport type LoopbackMetadataGetter = (\n url: string,\n) => Awaitable<OAuthClientMetadataInput>\n\nexport class ClientManager {\n protected readonly jwks: CachedGetter<string, Jwks>\n protected readonly metadataGetter: CachedGetter<string, OAuthClientMetadata>\n\n constructor(\n protected readonly serverMetadata: OAuthAuthorizationServerMetadata,\n protected readonly keyset: Keyset,\n protected readonly hooks: OAuthHooks,\n protected readonly store: ClientStore | null,\n protected readonly loopbackMetadata: LoopbackMetadataGetter | null = null,\n safeFetch: Fetch,\n clientJwksCache: SimpleStore<string, Jwks>,\n clientMetadataCache: SimpleStore<string, OAuthClientMetadata>,\n ) {\n const fetch = bindFetch(safeFetch)\n\n this.jwks = new CachedGetter(async (uri, options) => {\n const jwks = await fetch(buildJsonGetRequest(uri, options)).then(\n fetchJwksHandler,\n )\n\n return jwks\n }, clientJwksCache)\n\n this.metadataGetter = new CachedGetter(async (uri, options) => {\n const metadata = await fetch(buildJsonGetRequest(uri, options)).then(\n fetchMetadataHandler,\n )\n\n // Validate within the getter to avoid caching invalid metadata\n return this.validateClientMetadata(uri, metadata)\n }, clientMetadataCache)\n }\n\n /**\n *\n * @see {@link https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 OIDC Client Registration}\n */\n public async getClient(clientId: ClientId) {\n const metadata = await this.getClientMetadata(clientId).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Unable to obtain client metadata for \"${clientId}\"`,\n )\n })\n\n const jwks = metadata.jwks_uri\n ? await this.jwks.get(metadata.jwks_uri).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Unable to obtain jwks from \"${metadata.jwks_uri}\" for \"${clientId}\"`,\n )\n })\n : undefined\n\n const partialInfo = await callAsync(this.hooks.getClientInfo, clientId, {\n metadata,\n jwks,\n }).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Rejected client information for \"${clientId}\"`,\n )\n })\n\n const isFirstParty = partialInfo?.isFirstParty ?? false\n const isTrusted = partialInfo?.isTrusted ?? isFirstParty\n\n return new Client(clientId, metadata, jwks, { isFirstParty, isTrusted })\n }\n\n public async loadClients(\n clientIds: Iterable<ClientId>,\n {\n onError = (err) => {\n throw err\n },\n }: {\n onError?: (\n err: unknown,\n clientId: ClientId,\n ) => Awaitable<Client | null | undefined>\n } = {},\n ): Promise<Map<ClientId, Client>> {\n // Make sure we don't load the same client multiple times\n const uniqueClientIds =\n clientIds instanceof Set ? clientIds : new Set(clientIds)\n\n // Load all (unique) clients in parallel\n const clients = await Promise.all(\n Array.from(uniqueClientIds, async (clientId) =>\n this.getClient(clientId).catch((err) => onError(err, clientId)),\n ),\n )\n\n // Return a map for easy lookups\n return new Map(\n clients\n .filter((c) => c != null && c instanceof Client)\n .map((c) => [c.id, c]),\n )\n }\n\n protected async getClientMetadata(\n clientId: ClientId,\n ): Promise<OAuthClientMetadata> {\n if (isOAuthClientIdLoopback(clientId)) {\n return this.getLoopbackClientMetadata(clientId)\n } else if (isOAuthClientIdDiscoverable(clientId)) {\n return this.getDiscoverableClientMetadata(clientId)\n } else if (this.store) {\n return this.getStoredClientMetadata(clientId)\n }\n\n throw new InvalidClientMetadataError(`Invalid client ID \"${clientId}\"`)\n }\n\n protected async getLoopbackClientMetadata(\n clientId: OAuthClientIdLoopback,\n ): Promise<OAuthClientMetadata> {\n const { loopbackMetadata } = this\n if (!loopbackMetadata) {\n throw new InvalidClientMetadataError('Loopback clients are not allowed')\n }\n\n const metadataRaw = await callAsync(loopbackMetadata, clientId).catch(\n (err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Invalid loopback client id \"${clientId}\"`,\n )\n },\n )\n\n const metadata = await oauthClientMetadataSchema\n .parseAsync(metadataRaw)\n .catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Invalid loopback client metadata for \"${clientId}\"`,\n )\n })\n\n return this.validateClientMetadata(clientId, metadata)\n }\n\n protected async getDiscoverableClientMetadata(\n clientId: OAuthClientIdDiscoverable,\n ): Promise<OAuthClientMetadata> {\n const metadataUrl = parseDiscoverableClientId(clientId)\n\n const metadata = await this.metadataGetter.get(metadataUrl.href)\n\n // Note: we do *not* re-validate the metadata here, as the metadata is\n // validated within the getter. This is to avoid double validation.\n //\n // return this.validateClientMetadata(metadataUrl.href, metadata)\n return metadata\n }\n\n protected async getStoredClientMetadata(\n clientId: ClientId,\n ): Promise<OAuthClientMetadata> {\n if (this.store) {\n const metadata = await this.store.findClient(clientId)\n return this.validateClientMetadata(clientId, metadata)\n }\n\n throw new InvalidClientMetadataError(`Invalid client ID \"${clientId}\"`)\n }\n\n /**\n * This method will ensure that the client metadata is valid w.r.t. the OAuth\n * and OIDC specifications. It will also ensure that the metadata is\n * compatible with the implementation of this library, and ATPROTO's\n * requirements.\n */\n protected validateClientMetadata(\n clientId: ClientId,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n if (metadata.jwks && metadata.jwks_uri) {\n throw new InvalidClientMetadataError(\n 'jwks_uri and jwks are mutually exclusive',\n )\n }\n\n // Known OIDC specific parameters\n for (const k of [\n 'default_max_age',\n 'userinfo_signed_response_alg',\n 'id_token_signed_response_alg',\n 'userinfo_encrypted_response_alg',\n ] as const) {\n if (metadata[k] != null) {\n throw new InvalidClientMetadataError(`Unsupported \"${k}\" parameter`)\n }\n }\n\n const clientUriUrl = metadata.client_uri\n ? new URL(metadata.client_uri)\n : null\n\n if (clientUriUrl && isLocalHostname(clientUriUrl.hostname)) {\n throw new InvalidClientMetadataError('client_uri hostname is invalid')\n }\n\n const scopes = metadata.scope?.split(' ')\n\n if (!scopes) {\n throw new InvalidClientMetadataError('Missing scope property')\n }\n\n if (!scopes.includes('atproto')) {\n throw new InvalidClientMetadataError('Missing \"atproto\" scope')\n }\n\n const dupScope = scopes?.find(isDuplicate)\n if (dupScope) {\n throw new InvalidClientMetadataError(`Duplicate scope \"${dupScope}\"`)\n }\n\n const dupGrantType = metadata.grant_types.find(isDuplicate)\n if (dupGrantType) {\n throw new InvalidClientMetadataError(\n `Duplicate grant type \"${dupGrantType}\"`,\n )\n }\n\n for (const grantType of metadata.grant_types) {\n switch (grantType) {\n case 'implicit':\n // Never allowed (unsafe)\n throw new InvalidClientMetadataError(\n `Grant type \"${grantType}\" is not allowed`,\n )\n\n // @TODO Add support (e.g. for first party client)\n // case 'client_credentials':\n // case 'password':\n case 'authorization_code':\n case 'refresh_token':\n if (!this.serverMetadata.grant_types_supported?.includes(grantType)) {\n throw new InvalidClientMetadataError(\n `Unsupported grant type \"${grantType}\"`,\n )\n }\n break\n\n default:\n throw new InvalidClientMetadataError(\n `Grant type \"${grantType}\" is not supported`,\n )\n }\n }\n\n if (metadata.client_id && metadata.client_id !== clientId) {\n throw new InvalidClientMetadataError('client_id does not match')\n }\n\n if (metadata.subject_type && metadata.subject_type !== 'public') {\n throw new InvalidClientMetadataError(\n 'Only \"public\" subject_type is supported',\n )\n }\n\n switch (metadata.token_endpoint_auth_method) {\n case 'none':\n if (metadata.token_endpoint_auth_signing_alg) {\n throw new InvalidClientMetadataError(\n `token_endpoint_auth_method \"none\" must not have token_endpoint_auth_signing_alg`,\n )\n }\n break\n\n case 'private_key_jwt':\n if (!metadata.jwks && !metadata.jwks_uri) {\n throw new InvalidClientMetadataError(\n `private_key_jwt auth method requires jwks or jwks_uri`,\n )\n }\n if (metadata.jwks?.keys.length === 0) {\n throw new InvalidClientMetadataError(\n `private_key_jwt auth method requires at least one key in jwks`,\n )\n }\n if (!metadata.token_endpoint_auth_signing_alg) {\n throw new InvalidClientMetadataError(\n `Missing token_endpoint_auth_signing_alg client metadata`,\n )\n }\n break\n\n default:\n throw new InvalidClientMetadataError(\n `Unsupported client authentication method \"${metadata.token_endpoint_auth_method}\". Make sure \"token_endpoint_auth_method\" is set to one of: \"${Client.AUTH_METHODS_SUPPORTED.join('\", \"')}\"`,\n )\n }\n\n if (metadata.authorization_encrypted_response_enc) {\n throw new InvalidClientMetadataError(\n 'Encrypted authorization response is not supported',\n )\n }\n\n if (metadata.tls_client_certificate_bound_access_tokens) {\n throw new InvalidClientMetadataError(\n 'Mutual-TLS bound access tokens are not supported',\n )\n }\n\n if (\n metadata.authorization_encrypted_response_enc &&\n !metadata.authorization_encrypted_response_alg\n ) {\n throw new InvalidClientMetadataError(\n 'authorization_encrypted_response_enc requires authorization_encrypted_response_alg',\n )\n }\n\n // ATPROTO spec requires the use of DPoP (OAuth spec defaults to false)\n if (metadata.dpop_bound_access_tokens !== true) {\n throw new InvalidClientMetadataError(\n '\"dpop_bound_access_tokens\" must be true',\n )\n }\n\n // ATPROTO spec requires the use of PKCE, does not support OIDC\n if (!metadata.response_types.includes('code')) {\n throw new InvalidClientMetadataError('response_types must include \"code\"')\n } else if (!metadata.grant_types.includes('authorization_code')) {\n // Consistency check\n throw new InvalidClientMetadataError(\n `The \"code\" response type requires that \"grant_types\" contains \"authorization_code\"`,\n )\n }\n\n if (metadata.authorization_details_types?.length) {\n const dupAuthDetailsType =\n metadata.authorization_details_types.find(isDuplicate)\n if (dupAuthDetailsType) {\n throw new InvalidClientMetadataError(\n `Duplicate authorization_details_type \"${dupAuthDetailsType}\"`,\n )\n }\n\n const authorizationDetailsTypesSupported =\n this.serverMetadata.authorization_details_types_supported\n if (!authorizationDetailsTypesSupported) {\n throw new InvalidClientMetadataError(\n 'authorization_details_types are not supported',\n )\n }\n for (const type of metadata.authorization_details_types) {\n if (!authorizationDetailsTypesSupported.includes(type)) {\n throw new InvalidClientMetadataError(\n `Unsupported authorization_details_type \"${type}\"`,\n )\n }\n }\n }\n\n if (!metadata.redirect_uris?.length) {\n // ATPROTO spec requires that at least one redirect URI is provided\n\n throw new InvalidClientMetadataError(\n 'At least one redirect_uri is required',\n )\n }\n\n if (\n metadata.application_type === 'native' &&\n metadata.token_endpoint_auth_method !== 'none'\n ) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > Except when using a mechanism like Dynamic Client Registration\n // > [RFC7591] to provision per-instance secrets, native apps are\n // > classified as public clients, as defined by Section 2.1 of OAuth 2.0\n // > [RFC6749]; they MUST be registered with the authorization server as\n // > such. Authorization servers MUST record the client type in the client\n // > registration details in order to identify and process requests\n // > accordingly.\n\n // @NOTE We may want to remove this restriction in the future, for example\n // if https://github.com/bluesky-social/proposals/tree/main/0010-client-assertion-backend\n // gets adopted\n\n throw new InvalidClientMetadataError(\n 'Native clients must authenticate using \"none\" method',\n )\n }\n\n if (\n metadata.application_type === 'web' &&\n metadata.grant_types.includes('implicit')\n ) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Web Clients [as defined by \"application_type\"] using the OAuth\n // > Implicit Grant Type MUST only register URLs using the https\n // > scheme as redirect_uris; they MUST NOT use localhost as the\n // > hostname.\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n if (url.protocol !== 'https:') {\n throw new InvalidRedirectUriError(\n `Web clients must use HTTPS redirect URIs`,\n )\n }\n\n if (url.hostname === 'localhost') {\n throw new InvalidRedirectUriError(\n `Web clients must not use localhost as the hostname`,\n )\n }\n }\n }\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n\n if (url.username || url.password) {\n // Is this a valid concern? Should we allow credentials in the URI?\n throw new InvalidRedirectUriError(\n `Redirect URI ${url} must not contain credentials`,\n )\n }\n\n switch (true) {\n // FIRST: Loopback redirect URI exception (only for native apps)\n\n case url.hostname === 'localhost': {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3\n //\n // > While redirect URIs using localhost (i.e.,\n // > \"http://localhost:{port}/{path}\") function similarly to loopback IP\n // > redirects described in Section 7.3, the use of localhost is NOT\n // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal\n // > rather than localhost avoids inadvertently listening on network\n // > interfaces other than the loopback interface. It is also less\n // > susceptible to client-side firewalls and misconfigured host name\n // > resolution on the user's device.\n throw new InvalidRedirectUriError(\n `Loopback redirect URI ${url} is not allowed (use explicit IPs instead)`,\n )\n }\n\n case url.hostname === '127.0.0.1':\n case url.hostname === '[::1]': {\n // Only allowed for native apps\n if (metadata.application_type !== 'native') {\n throw new InvalidRedirectUriError(\n `Loopback redirect URIs are only allowed for native apps`,\n )\n }\n\n if (url.port) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3\n //\n // > The authorization server MUST allow any port to be specified at\n // > the time of the request for loopback IP redirect URIs, to\n // > accommodate clients that obtain an available ephemeral port\n // > from the operating system at the time of the request.\n //\n // Note: although validation of the redirect_uri will ignore the\n // port we still allow it to be specified, as the spec does not\n // forbid it. If a port number is specified, ports will need to\n // match when validating authorization requests. See\n // \"compareRedirectUri()\".\n }\n\n if (url.protocol !== 'http:') {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3\n //\n // > Loopback redirect URIs use the \"http\" scheme and are constructed\n // > with the loopback IP literal and whatever port the client is\n // > listening on. That is, \"http://127.0.0.1:{port}/{path}\" for IPv4,\n // > and \"http://[::1]:{port}/{path}\" for IPv6.\n throw new InvalidRedirectUriError(\n `Loopback redirect URI ${url} must use HTTP`,\n )\n }\n\n break\n }\n\n // SECOND: Protocol-based URI Redirection\n\n case url.protocol === 'http:': {\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > request_uri [...] URLs MUST use the https scheme unless the\n // > target Request Object is signed in a way that is verifiable by\n // > the OP.\n //\n // OIDC/Request Object are not supported. ATproto spec should not\n // allow HTTP redirect URIs either.\n\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Authorization Servers MAY reject Redirection URI values using\n // > the http scheme, other than the loopback case for Native\n // > Clients.\n throw new InvalidRedirectUriError(\n 'Only loopback redirect URIs are allowed to use the \"http\" scheme',\n )\n }\n\n case url.protocol === 'https:': {\n if (isLocalHostname(url.hostname)) {\n throw new InvalidRedirectUriError(\n `Redirect URI \"${url}\"'s domain name must not be a local hostname`,\n )\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a\n // > URI scheme based on a domain name that is under the control of\n // > the app can help to prove ownership in the event of a dispute\n // > where two apps claim the same private-use URI scheme (where one\n // > app is acting maliciously).\n //\n // We can't enforce this here (in generic client validation) because\n // we don't have a concept of generic proven ownership.\n //\n // Discoverable clients, however, will have this check covered in the\n // `validateDiscoverableClientMetadata`, by using the client_id's\n // domain as \"proven ownership\".\n\n // The following restriction from OIDC is *not* enforced for clients\n // as it prevents \"App Links\" / \"Apple Universal Links\" from being\n // used as redirect URIs.\n //\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Native Clients [as defined by \"application_type\"] MUST only\n // > register redirect_uris using custom URI schemes or loopback URLs\n // > using the http scheme; loopback URLs use localhost or the IP\n // > loopback literals 127.0.0.1 or [::1] as the hostname.\n //\n // if (metadata.application_type === 'native') {\n // throw new InvalidRedirectUriError(\n // `Native clients must use custom URI schemes or loopback URLs`,\n // )\n // }\n\n break\n }\n\n case isPrivateUseUriScheme(url): {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n //\n // > When choosing a URI scheme to associate with the app, apps MUST\n // > use a URI scheme based on a domain name under their control,\n // > expressed in reverse order, as recommended by Section 3.8 of\n // > [RFC7595] for private-use URI schemes.\n\n if (metadata.application_type !== 'native') {\n throw new InvalidRedirectUriError(\n `Private-Use URI Scheme redirect URI are only allowed for native apps`,\n )\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a\n // > URI scheme based on a domain name that is under the control of\n // > the app can help to prove ownership in the event of a dispute\n // > where two apps claim the same private-use URI scheme (where one\n // > app is acting maliciously).\n //\n // We can't check for ownership here (as there is no concept of\n // proven ownership in the generic client validation), but we can\n // check that the domain is a valid domain name.\n\n const urlDomain = reverseDomain(url.protocol.slice(0, -1))\n\n if (isLocalHostname(urlDomain)) {\n throw new InvalidRedirectUriError(\n `Private-use URI Scheme redirect URI must not be a local hostname`,\n )\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n //\n // > Following the requirements of Section 3.2 of [RFC3986], as there\n // > is no naming authority for private-use URI scheme redirects, only\n // > a single slash (\"/\") appears after the scheme component.\n if (\n url.href.startsWith(`${url.protocol}//`) ||\n url.username ||\n url.password ||\n url.hostname ||\n url.port\n ) {\n throw new InvalidRedirectUriError(\n `Private-Use URI Scheme must be in the form ${url.protocol}/<path>`,\n )\n }\n\n break\n }\n\n default:\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > At a minimum, any private-use URI scheme that doesn't contain a\n // > period character (\".\") SHOULD be rejected.\n throw new InvalidRedirectUriError(\n `Invalid redirect URI scheme \"${url.protocol}\"`,\n )\n }\n }\n\n if (isOAuthClientIdLoopback(clientId)) {\n return this.validateLoopbackClientMetadata(clientId, metadata)\n } else if (isOAuthClientIdDiscoverable(clientId)) {\n return this.validateDiscoverableClientMetadata(clientId, metadata)\n } else {\n return metadata\n }\n }\n\n validateLoopbackClientMetadata(\n clientId: OAuthClientIdLoopback,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n if (metadata.client_uri) {\n throw new InvalidClientMetadataError(\n 'client_uri is not allowed for loopback clients',\n )\n }\n\n if (metadata.application_type !== 'native') {\n throw new InvalidClientMetadataError(\n 'Loopback clients must have application_type \"native\"',\n )\n }\n\n const method = metadata.token_endpoint_auth_method\n if (method !== 'none') {\n throw new InvalidClientMetadataError(\n `Loopback clients are not allowed to use \"token_endpoint_auth_method\" ${method}`,\n )\n }\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n\n if (url.protocol !== 'http:') {\n throw new InvalidRedirectUriError(\n `Loopback clients must use HTTP redirect URIs`,\n )\n }\n\n if (!isLoopbackHost(url.hostname)) {\n throw new InvalidRedirectUriError(\n `Loopback clients must use loopback redirect URIs`,\n )\n }\n }\n\n return metadata\n }\n\n validateDiscoverableClientMetadata(\n clientId: OAuthClientIdDiscoverable,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n if (!metadata.client_id) {\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html\n throw new InvalidClientMetadataError(\n `client_id is required for discoverable clients`,\n )\n }\n\n const clientIdUrl = parseDiscoverableClientId(clientId)\n\n if (metadata.client_uri) {\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html\n //\n // The client_uri must be a parent of the client_id URL. This might be\n // relaxed in the future.\n\n const clientUriUrl = new URL(metadata.client_uri)\n\n if (clientUriUrl.origin !== clientIdUrl.origin) {\n throw new InvalidClientMetadataError(\n `client_uri must have the same origin as the client_id`,\n )\n }\n\n if (clientIdUrl.pathname !== clientUriUrl.pathname) {\n if (\n !clientIdUrl.pathname.startsWith(\n clientUriUrl.pathname.endsWith('/')\n ? clientUriUrl.pathname\n : `${clientUriUrl.pathname}/`,\n )\n ) {\n throw new InvalidClientMetadataError(\n `client_uri must be a parent URL of the client_id`,\n )\n }\n }\n }\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n\n if (isPrivateUseUriScheme(url)) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a\n // > URI scheme based on a domain name that is under the control of\n // > the app can help to prove ownership in the event of a dispute\n // > where two apps claim the same private-use URI scheme (where one\n // > app is acting maliciously).\n\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html\n //\n // Fully qualified domain name (FQDN) of the client_id, in reverse\n // order. This could be relaxed to allow same apex domain names, or\n // parent domains, but for now we require an exact match.\n const protocol = `${reverseDomain(clientIdUrl.hostname)}:`\n if (url.protocol !== protocol) {\n throw new InvalidRedirectUriError(\n `Private-Use URI Scheme redirect URI, for discoverable client metadata, must be the fully qualified domain name (FQDN) of the client_id, in reverse order (${protocol})`,\n )\n }\n }\n }\n\n return metadata\n }\n}\n\nfunction isDuplicate<\n T extends string | number | boolean | null | undefined | symbol,\n>(value: T, index: number, array: T[]) {\n return array.includes(value, index + 1)\n}\n\nfunction reverseDomain(domain: string) {\n return domain.split('.').reverse().join('.')\n}\n\nfunction isPrivateUseUriScheme(uri: URL) {\n return uri.protocol.includes('.')\n}\n\nfunction buildJsonGetRequest(uri: string, options?: GetCachedOptions) {\n return new Request(uri, {\n headers: { accept: 'application/json' },\n // @ts-expect-error invalid types in \"undici-types\"\n cache: options?.noCache ? 'no-cache' : undefined,\n signal: options?.signal,\n redirect: 'error',\n })\n}\n"]}
1
+ {"version":3,"file":"client-manager.js","sourceRoot":"","sources":["../../src/client/client-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { Jwks, Keyset, jwksPubSchema } from '@atproto/jwk'\nimport {\n OAuthAuthorizationServerMetadata,\n OAuthClientIdDiscoverable,\n OAuthClientIdLoopback,\n OAuthClientMetadata,\n OAuthClientMetadataInput,\n isLocalHostname,\n isOAuthClientIdDiscoverable,\n isOAuthClientIdLoopback,\n oauthClientMetadataSchema,\n} from '@atproto/oauth-types'\nimport {\n Fetch,\n bindFetch,\n fetchJsonProcessor,\n fetchJsonZodProcessor,\n fetchOkProcessor,\n} from '@atproto-labs/fetch'\nimport { pipe } from '@atproto-labs/pipe'\nimport {\n CachedGetter,\n GetCachedOptions,\n SimpleStore,\n} from '@atproto-labs/simple-store'\nimport { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js'\nimport { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'\nimport { callAsync } from '../lib/util/function.js'\nimport { Awaitable } from '../lib/util/type.js'\nimport { OAuthHooks } from '../oauth-hooks.js'\nimport { ClientId } from './client-id.js'\nimport { ClientStore } from './client-store.js'\nimport { parseDiscoverableClientId, parseRedirectUri } from './client-utils.js'\nimport { Client } from './client.js'\n\nconst fetchMetadataHandler = pipe(\n fetchOkProcessor(),\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1\n fetchJsonProcessor('application/json', true),\n fetchJsonZodProcessor(oauthClientMetadataSchema),\n)\n\nconst fetchJwksHandler = pipe(\n fetchOkProcessor(),\n fetchJsonProcessor('application/json', false),\n fetchJsonZodProcessor(jwksPubSchema),\n)\n\nexport type LoopbackMetadataGetter = (\n url: string,\n) => Awaitable<OAuthClientMetadataInput>\n\nexport class ClientManager {\n protected readonly jwks: CachedGetter<string, Jwks>\n protected readonly metadataGetter: CachedGetter<string, OAuthClientMetadata>\n\n constructor(\n protected readonly serverMetadata: OAuthAuthorizationServerMetadata,\n protected readonly keyset: Keyset,\n protected readonly hooks: OAuthHooks,\n protected readonly store: ClientStore | null,\n protected readonly loopbackMetadata: LoopbackMetadataGetter | null = null,\n safeFetch: Fetch,\n clientJwksCache: SimpleStore<string, Jwks>,\n clientMetadataCache: SimpleStore<string, OAuthClientMetadata>,\n ) {\n const fetch = bindFetch(safeFetch)\n\n this.jwks = new CachedGetter(async (uri, options) => {\n const jwks = await fetch(buildJsonGetRequest(uri, options)).then(\n fetchJwksHandler,\n )\n\n return jwks\n }, clientJwksCache)\n\n this.metadataGetter = new CachedGetter(async (uri, options) => {\n const metadata = await fetch(buildJsonGetRequest(uri, options)).then(\n fetchMetadataHandler,\n )\n\n // Validate within the getter to avoid caching invalid metadata\n return this.validateClientMetadata(uri, metadata)\n }, clientMetadataCache)\n }\n\n /**\n *\n * @see {@link https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 OIDC Client Registration}\n */\n public async getClient(clientId: ClientId) {\n const metadata = await this.getClientMetadata(clientId).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Unable to obtain client metadata for \"${clientId}\"`,\n )\n })\n\n const jwks = metadata.jwks_uri\n ? await this.jwks.get(metadata.jwks_uri).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Unable to obtain jwks from \"${metadata.jwks_uri}\" for \"${clientId}\"`,\n )\n })\n : undefined\n\n const partialInfo = await callAsync(this.hooks.getClientInfo, clientId, {\n metadata,\n jwks,\n }).catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Rejected client information for \"${clientId}\"`,\n )\n })\n\n const isFirstParty = partialInfo?.isFirstParty ?? false\n const isTrusted = partialInfo?.isTrusted ?? isFirstParty\n\n return new Client(clientId, metadata, jwks, { isFirstParty, isTrusted })\n }\n\n public async loadClients(\n clientIds: Iterable<ClientId>,\n {\n onError = (err) => {\n throw err\n },\n }: {\n onError?: (\n err: unknown,\n clientId: ClientId,\n ) => Awaitable<Client | null | undefined>\n } = {},\n ): Promise<Map<ClientId, Client>> {\n // Make sure we don't load the same client multiple times\n const uniqueClientIds =\n clientIds instanceof Set ? clientIds : new Set(clientIds)\n\n // Load all (unique) clients in parallel\n const clients = await Promise.all(\n Array.from(uniqueClientIds, async (clientId) =>\n this.getClient(clientId).catch((err) => onError(err, clientId)),\n ),\n )\n\n // Return a map for easy lookups\n return new Map(\n clients\n .filter((c) => c != null && c instanceof Client)\n .map((c) => [c.id, c]),\n )\n }\n\n protected async getClientMetadata(\n clientId: ClientId,\n ): Promise<OAuthClientMetadata> {\n if (isOAuthClientIdLoopback(clientId)) {\n return this.getLoopbackClientMetadata(clientId)\n } else if (isOAuthClientIdDiscoverable(clientId)) {\n return this.getDiscoverableClientMetadata(clientId)\n } else if (this.store) {\n return this.getStoredClientMetadata(clientId)\n }\n\n throw new InvalidClientMetadataError(`Invalid client ID \"${clientId}\"`)\n }\n\n protected async getLoopbackClientMetadata(\n clientId: OAuthClientIdLoopback,\n ): Promise<OAuthClientMetadata> {\n const { loopbackMetadata } = this\n if (!loopbackMetadata) {\n throw new InvalidClientMetadataError('Loopback clients are not allowed')\n }\n\n const metadataRaw = await callAsync(loopbackMetadata, clientId).catch(\n (err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Invalid loopback client id \"${clientId}\"`,\n )\n },\n )\n\n const metadata = await oauthClientMetadataSchema\n .parseAsync(metadataRaw)\n .catch((err) => {\n throw InvalidClientMetadataError.from(\n err,\n `Invalid loopback client metadata for \"${clientId}\"`,\n )\n })\n\n return this.validateClientMetadata(clientId, metadata)\n }\n\n protected async getDiscoverableClientMetadata(\n clientId: OAuthClientIdDiscoverable,\n ): Promise<OAuthClientMetadata> {\n const metadataUrl = parseDiscoverableClientId(clientId)\n\n const metadata = await this.metadataGetter.get(metadataUrl.href)\n\n // Note: we do *not* re-validate the metadata here, as the metadata is\n // validated within the getter. This is to avoid double validation.\n //\n // return this.validateClientMetadata(metadataUrl.href, metadata)\n return metadata\n }\n\n protected async getStoredClientMetadata(\n clientId: ClientId,\n ): Promise<OAuthClientMetadata> {\n if (this.store) {\n const metadata = await this.store.findClient(clientId)\n return this.validateClientMetadata(clientId, metadata)\n }\n\n throw new InvalidClientMetadataError(`Invalid client ID \"${clientId}\"`)\n }\n\n /**\n * This method will ensure that the client metadata is valid w.r.t. the OAuth\n * and OIDC specifications. It will also ensure that the metadata is\n * compatible with the implementation of this library, and ATPROTO's\n * requirements.\n */\n protected validateClientMetadata(\n clientId: ClientId,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n // @TODO This method should only check for rules that are specific to this\n // implementation or the ATPROTO specification. All generic validation rules\n // should be moved to the @atproto/oauth-types package.\n\n if (metadata.jwks && metadata.jwks_uri) {\n throw new InvalidClientMetadataError(\n 'jwks_uri and jwks are mutually exclusive',\n )\n }\n\n // Known OIDC specific parameters\n for (const k of [\n 'default_max_age',\n 'userinfo_signed_response_alg',\n 'id_token_signed_response_alg',\n 'userinfo_encrypted_response_alg',\n ] as const) {\n if (metadata[k] != null) {\n throw new InvalidClientMetadataError(`Unsupported \"${k}\" parameter`)\n }\n }\n\n const clientUriUrl = metadata.client_uri\n ? new URL(metadata.client_uri)\n : null\n\n if (clientUriUrl && isLocalHostname(clientUriUrl.hostname)) {\n throw new InvalidClientMetadataError('client_uri hostname is invalid')\n }\n\n const scopes = metadata.scope?.split(' ')\n\n if (!scopes) {\n throw new InvalidClientMetadataError('Missing scope property')\n }\n\n if (!scopes.includes('atproto')) {\n throw new InvalidClientMetadataError('Missing \"atproto\" scope')\n }\n\n const dupScope = scopes?.find(isDuplicate)\n if (dupScope) {\n throw new InvalidClientMetadataError(`Duplicate scope \"${dupScope}\"`)\n }\n\n const dupGrantType = metadata.grant_types.find(isDuplicate)\n if (dupGrantType) {\n throw new InvalidClientMetadataError(\n `Duplicate grant type \"${dupGrantType}\"`,\n )\n }\n\n for (const grantType of metadata.grant_types) {\n switch (grantType) {\n case 'implicit':\n // Never allowed (unsafe)\n throw new InvalidClientMetadataError(\n `Grant type \"${grantType}\" is not allowed`,\n )\n\n // @TODO Add support (e.g. for first party client)\n // case 'client_credentials':\n // case 'password':\n case 'authorization_code':\n case 'refresh_token':\n if (!this.serverMetadata.grant_types_supported?.includes(grantType)) {\n throw new InvalidClientMetadataError(\n `Unsupported grant type \"${grantType}\"`,\n )\n }\n break\n\n default:\n throw new InvalidClientMetadataError(\n `Grant type \"${grantType}\" is not supported`,\n )\n }\n }\n\n if (metadata.client_id && metadata.client_id !== clientId) {\n throw new InvalidClientMetadataError('client_id does not match')\n }\n\n if (metadata.subject_type && metadata.subject_type !== 'public') {\n throw new InvalidClientMetadataError(\n 'Only \"public\" subject_type is supported',\n )\n }\n\n switch (metadata.token_endpoint_auth_method) {\n case 'none':\n if (metadata.token_endpoint_auth_signing_alg) {\n throw new InvalidClientMetadataError(\n `token_endpoint_auth_method \"none\" must not have token_endpoint_auth_signing_alg`,\n )\n }\n break\n\n case 'private_key_jwt':\n if (!metadata.jwks && !metadata.jwks_uri) {\n throw new InvalidClientMetadataError(\n `private_key_jwt auth method requires jwks or jwks_uri`,\n )\n }\n if (metadata.jwks?.keys.length === 0) {\n throw new InvalidClientMetadataError(\n `private_key_jwt auth method requires at least one key in jwks`,\n )\n }\n if (!metadata.token_endpoint_auth_signing_alg) {\n throw new InvalidClientMetadataError(\n `Missing token_endpoint_auth_signing_alg client metadata`,\n )\n }\n break\n\n default:\n throw new InvalidClientMetadataError(\n `Unsupported client authentication method \"${metadata.token_endpoint_auth_method}\". Make sure \"token_endpoint_auth_method\" is set to one of: \"${Client.AUTH_METHODS_SUPPORTED.join('\", \"')}\"`,\n )\n }\n\n if (metadata.authorization_encrypted_response_enc) {\n throw new InvalidClientMetadataError(\n 'Encrypted authorization response is not supported',\n )\n }\n\n if (metadata.tls_client_certificate_bound_access_tokens) {\n throw new InvalidClientMetadataError(\n 'Mutual-TLS bound access tokens are not supported',\n )\n }\n\n if (\n metadata.authorization_encrypted_response_enc &&\n !metadata.authorization_encrypted_response_alg\n ) {\n throw new InvalidClientMetadataError(\n 'authorization_encrypted_response_enc requires authorization_encrypted_response_alg',\n )\n }\n\n // ATPROTO spec requires the use of DPoP (OAuth spec defaults to false)\n if (metadata.dpop_bound_access_tokens !== true) {\n throw new InvalidClientMetadataError(\n '\"dpop_bound_access_tokens\" must be true',\n )\n }\n\n // ATPROTO spec requires the use of PKCE, does not support OIDC\n if (!metadata.response_types.includes('code')) {\n throw new InvalidClientMetadataError('response_types must include \"code\"')\n } else if (!metadata.grant_types.includes('authorization_code')) {\n // Consistency check\n throw new InvalidClientMetadataError(\n `The \"code\" response type requires that \"grant_types\" contains \"authorization_code\"`,\n )\n }\n\n if (metadata.authorization_details_types?.length) {\n const dupAuthDetailsType =\n metadata.authorization_details_types.find(isDuplicate)\n if (dupAuthDetailsType) {\n throw new InvalidClientMetadataError(\n `Duplicate authorization_details_type \"${dupAuthDetailsType}\"`,\n )\n }\n\n const authorizationDetailsTypesSupported =\n this.serverMetadata.authorization_details_types_supported\n if (!authorizationDetailsTypesSupported) {\n throw new InvalidClientMetadataError(\n 'authorization_details_types are not supported',\n )\n }\n for (const type of metadata.authorization_details_types) {\n if (!authorizationDetailsTypesSupported.includes(type)) {\n throw new InvalidClientMetadataError(\n `Unsupported authorization_details_type \"${type}\"`,\n )\n }\n }\n }\n\n if (!metadata.redirect_uris?.length) {\n // ATPROTO spec requires that at least one redirect URI is provided\n\n throw new InvalidClientMetadataError(\n 'At least one redirect_uri is required',\n )\n }\n\n if (\n metadata.application_type === 'native' &&\n metadata.token_endpoint_auth_method !== 'none'\n ) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > Except when using a mechanism like Dynamic Client Registration\n // > [RFC7591] to provision per-instance secrets, native apps are\n // > classified as public clients, as defined by Section 2.1 of OAuth 2.0\n // > [RFC6749]; they MUST be registered with the authorization server as\n // > such. Authorization servers MUST record the client type in the client\n // > registration details in order to identify and process requests\n // > accordingly.\n\n // @NOTE We may want to remove this restriction in the future, for example\n // if https://github.com/bluesky-social/proposals/tree/main/0010-client-assertion-backend\n // gets adopted\n\n throw new InvalidClientMetadataError(\n 'Native clients must authenticate using \"none\" method',\n )\n }\n\n if (\n metadata.application_type === 'web' &&\n metadata.grant_types.includes('implicit')\n ) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Web Clients [as defined by \"application_type\"] using the OAuth\n // > Implicit Grant Type MUST only register URLs using the https\n // > scheme as redirect_uris; they MUST NOT use localhost as the\n // > hostname.\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n if (url.protocol !== 'https:') {\n throw new InvalidRedirectUriError(\n `Web clients must use HTTPS redirect URIs`,\n )\n }\n\n if (url.hostname === 'localhost') {\n throw new InvalidRedirectUriError(\n `Web clients must not use localhost as the hostname`,\n )\n }\n }\n }\n\n for (const redirectUri of metadata.redirect_uris) {\n const url = parseRedirectUri(redirectUri)\n\n if (url.username || url.password) {\n // Is this a valid concern? Should we allow credentials in the URI?\n throw new InvalidRedirectUriError(\n `Redirect URI ${url} must not contain credentials`,\n )\n }\n\n switch (true) {\n // FIRST: Loopback redirect URI exception (only for native apps)\n\n case url.hostname === 'localhost': {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3\n //\n // > While redirect URIs using localhost (i.e.,\n // > \"http://localhost:{port}/{path}\") function similarly to loopback IP\n // > redirects described in Section 7.3, the use of localhost is NOT\n // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal\n // > rather than localhost avoids inadvertently listening on network\n // > interfaces other than the loopback interface. It is also less\n // > susceptible to client-side firewalls and misconfigured host name\n // > resolution on the user's device.\n throw new InvalidRedirectUriError(\n `Loopback redirect URI ${url} is not allowed (use explicit IPs instead)`,\n )\n }\n\n case url.hostname === '127.0.0.1':\n case url.hostname === '[::1]': {\n // Only allowed for native apps\n if (metadata.application_type !== 'native') {\n throw new InvalidRedirectUriError(\n `Loopback redirect URIs are only allowed for native apps`,\n )\n }\n\n if (url.port) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3\n //\n // > The authorization server MUST allow any port to be specified at\n // > the time of the request for loopback IP redirect URIs, to\n // > accommodate clients that obtain an available ephemeral port\n // > from the operating system at the time of the request.\n //\n // Note: although validation of the redirect_uri will ignore the\n // port we still allow it to be specified, as the spec does not\n // forbid it. If a port number is specified, ports will need to\n // match when validating authorization requests. See\n // \"compareRedirectUri()\".\n }\n\n if (url.protocol !== 'http:') {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3\n //\n // > Loopback redirect URIs use the \"http\" scheme and are constructed\n // > with the loopback IP literal and whatever port the client is\n // > listening on. That is, \"http://127.0.0.1:{port}/{path}\" for IPv4,\n // > and \"http://[::1]:{port}/{path}\" for IPv6.\n throw new InvalidRedirectUriError(\n `Loopback redirect URI ${url} must use HTTP`,\n )\n }\n\n break\n }\n\n // SECOND: Protocol-based URI Redirection\n\n case url.protocol === 'http:': {\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > request_uri [...] URLs MUST use the https scheme unless the\n // > target Request Object is signed in a way that is verifiable by\n // > the OP.\n //\n // OIDC/Request Object are not supported. ATproto spec should not\n // allow HTTP redirect URIs either.\n\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Authorization Servers MAY reject Redirection URI values using\n // > the http scheme, other than the loopback case for Native\n // > Clients.\n throw new InvalidRedirectUriError(\n 'Only loopback redirect URIs are allowed to use the \"http\" scheme',\n )\n }\n\n case url.protocol === 'https:': {\n if (isLocalHostname(url.hostname)) {\n throw new InvalidRedirectUriError(\n `Redirect URI \"${url}\"'s domain name must not be a local hostname`,\n )\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a\n // > URI scheme based on a domain name that is under the control of\n // > the app can help to prove ownership in the event of a dispute\n // > where two apps claim the same private-use URI scheme (where one\n // > app is acting maliciously).\n //\n // We can't enforce this here (in generic client validation) because\n // we don't have a concept of generic proven ownership.\n //\n // Discoverable clients, however, will have this check covered in the\n // `validateDiscoverableClientMetadata`, by using the client_id's\n // domain as \"proven ownership\".\n\n // The following restriction from OIDC is *not* enforced for clients\n // as it prevents \"App Links\" / \"Apple Universal Links\" from being\n // used as redirect URIs.\n //\n // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2\n //\n // > Native Clients [as defined by \"application_type\"] MUST only\n // > register redirect_uris using custom URI schemes or loopback URLs\n // > using the http scheme; loopback URLs use localhost or the IP\n // > loopback literals 127.0.0.1 or [::1] as the hostname.\n //\n // if (metadata.application_type === 'native') {\n // throw new InvalidRedirectUriError(\n // `Native clients must use custom URI schemes or loopback URLs`,\n // )\n // }\n\n break\n }\n\n case isPrivateUseUriScheme(url): {\n if (metadata.application_type !== 'native') {\n throw new InvalidRedirectUriError(\n `Private-Use URI Scheme redirect URI are only allowed for native apps`,\n )\n }\n\n break\n }\n\n default:\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > At a minimum, any private-use URI scheme that doesn't contain a\n // > period character (\".\") SHOULD be rejected.\n throw new InvalidRedirectUriError(\n `Invalid redirect URI scheme \"${url.protocol}\"`,\n )\n }\n }\n\n if (isOAuthClientIdLoopback(clientId)) {\n return this.validateLoopbackClientMetadata(clientId, metadata)\n } else if (isOAuthClientIdDiscoverable(clientId)) {\n return this.validateDiscoverableClientMetadata(clientId, metadata)\n } else {\n return metadata\n }\n }\n\n validateLoopbackClientMetadata(\n clientId: OAuthClientIdLoopback,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n if (metadata.client_uri) {\n throw new InvalidClientMetadataError(\n 'client_uri is not allowed for loopback clients',\n )\n }\n\n if (metadata.application_type !== 'native') {\n throw new InvalidClientMetadataError(\n 'Loopback clients must have application_type \"native\"',\n )\n }\n\n const method = metadata.token_endpoint_auth_method\n if (method !== 'none') {\n throw new InvalidClientMetadataError(\n `Loopback clients are not allowed to use \"token_endpoint_auth_method\" ${method}`,\n )\n }\n\n return metadata\n }\n\n validateDiscoverableClientMetadata(\n clientId: OAuthClientIdDiscoverable,\n metadata: OAuthClientMetadata,\n ): OAuthClientMetadata {\n if (!metadata.client_id) {\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html\n throw new InvalidClientMetadataError(\n `client_id is required for discoverable clients`,\n )\n }\n\n const clientIdUrl = parseDiscoverableClientId(clientId)\n\n if (metadata.client_uri) {\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html\n //\n // The client_uri must be a parent of the client_id URL. This might be\n // relaxed in the future.\n\n const clientUriUrl = new URL(metadata.client_uri)\n\n if (clientUriUrl.origin !== clientIdUrl.origin) {\n throw new InvalidClientMetadataError(\n `client_uri must have the same origin as the client_id`,\n )\n }\n\n if (clientIdUrl.pathname !== clientUriUrl.pathname) {\n if (\n !clientIdUrl.pathname.startsWith(\n clientUriUrl.pathname.endsWith('/')\n ? clientUriUrl.pathname\n : `${clientUriUrl.pathname}/`,\n )\n ) {\n throw new InvalidClientMetadataError(\n `client_uri must be a parent URL of the client_id`,\n )\n }\n }\n }\n\n for (const redirectUri of metadata.redirect_uris) {\n // @NOTE at this point, all redirect URIs have already been validated by\n // oauthRedirectUriSchema\n\n const url = parseRedirectUri(redirectUri)\n\n if (isPrivateUseUriScheme(url)) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n //\n // > When choosing a URI scheme to associate with the app, apps MUST use\n // > a URI scheme based on a domain name under their control, expressed\n // > in reverse order, as recommended by Section 3.8 of [RFC7595] for\n // > private-use URI schemes.\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a\n // > URI scheme based on a domain name that is under the control of\n // > the app can help to prove ownership in the event of a dispute\n // > where two apps claim the same private-use URI scheme (where one\n // > app is acting maliciously).\n\n // https://atproto.com/specs/oauth\n //\n // > Any custom scheme must match the client_id hostname in\n // > reverse-domain order. The URI scheme must be followed by a single\n // > colon (:) then a single forward slash (/) and then a URI path\n // > component. For example, an app with client_id\n // > https://app.example.com/client-metadata.json could have a\n // > redirect_uri of com.example.app:/callback.\n const protocol = `${reverseDomain(clientIdUrl.hostname)}:`\n if (url.protocol !== protocol) {\n throw new InvalidRedirectUriError(\n `Private-Use URI Scheme redirect URI, for discoverable client metadata, must be the fully qualified domain name (FQDN) of the client_id, in reverse order (${protocol})`,\n )\n }\n }\n }\n\n return metadata\n }\n}\n\nfunction isDuplicate<\n T extends string | number | boolean | null | undefined | symbol,\n>(value: T, index: number, array: T[]) {\n return array.includes(value, index + 1)\n}\n\nfunction reverseDomain(domain: string) {\n return domain.split('.').reverse().join('.')\n}\n\nfunction isPrivateUseUriScheme(uri: URL) {\n return uri.protocol.includes('.')\n}\n\nfunction buildJsonGetRequest(uri: string, options?: GetCachedOptions) {\n return new Request(uri, {\n headers: { accept: 'application/json' },\n // @ts-expect-error invalid types in \"undici-types\"\n cache: options?.noCache ? 'no-cache' : undefined,\n signal: options?.signal,\n redirect: 'error',\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client-utils.d.ts","sourceRoot":"","sources":["../../src/client/client-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EAE1B,MAAM,sBAAsB,CAAA;AAK7B,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAMzD;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,yBAAyB,GAClC,GAAG,CAkBL"}
1
+ {"version":3,"file":"client-utils.d.ts","sourceRoot":"","sources":["../../src/client/client-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EAG1B,MAAM,sBAAsB,CAAA;AAI7B,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAMzD;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,yBAAyB,GAClC,GAAG,CAkBL"}
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseRedirectUri = parseRedirectUri;
4
4
  exports.parseDiscoverableClientId = parseDiscoverableClientId;
5
5
  const oauth_types_1 = require("@atproto/oauth-types");
6
- const fetch_node_1 = require("@atproto-labs/fetch-node");
7
6
  const invalid_client_id_error_js_1 = require("../errors/invalid-client-id-error.js");
8
7
  const invalid_redirect_uri_error_js_1 = require("../errors/invalid-redirect-uri-error.js");
9
8
  function parseRedirectUri(redirectUri) {
@@ -18,7 +17,7 @@ function parseDiscoverableClientId(clientId) {
18
17
  try {
19
18
  const url = (0, oauth_types_1.parseOAuthDiscoverableClientId)(clientId);
20
19
  // Extra validation, prevent usage of invalid internet domain names.
21
- if ((0, fetch_node_1.isLocalHostname)(url.hostname)) {
20
+ if ((0, oauth_types_1.isLocalHostname)(url.hostname)) {
22
21
  throw new invalid_client_id_error_js_1.InvalidClientIdError("The client_id's TLD must not be a local hostname");
23
22
  }
24
23
  return url;
@@ -1 +1 @@
1
- {"version":3,"file":"client-utils.js","sourceRoot":"","sources":["../../src/client/client-utils.ts"],"names":[],"mappings":";;AAQA,4CAMC;AAED,8DAoBC;AApCD,sDAG6B;AAC7B,yDAA0D;AAC1D,qFAA2E;AAC3E,2FAAiF;AAEjF,SAAgB,gBAAgB,CAAC,WAAmB;IAClD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,CAAA;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,uDAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;AACH,CAAC;AAED,SAAgB,yBAAyB,CACvC,QAAmC;IAEnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,4CAA8B,EAAC,QAAQ,CAAC,CAAA;QAEpD,oEAAoE;QACpE,IAAI,IAAA,4BAAe,EAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,iDAAoB,CAC5B,kDAAkD,CACnD,CAAA;QACH,CAAC;QAED,OAAO,GAAG,CAAA;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,iDAAoB,CAAC,IAAI,CAC7B,GAAG,EACH,wCAAwC,CACzC,CAAA;IACH,CAAC;AACH,CAAC","sourcesContent":["import {\n OAuthClientIdDiscoverable,\n parseOAuthDiscoverableClientId,\n} from '@atproto/oauth-types'\nimport { isLocalHostname } from '@atproto-labs/fetch-node'\nimport { InvalidClientIdError } from '../errors/invalid-client-id-error.js'\nimport { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'\n\nexport function parseRedirectUri(redirectUri: string): URL {\n try {\n return new URL(redirectUri)\n } catch (err) {\n throw InvalidRedirectUriError.from(err)\n }\n}\n\nexport function parseDiscoverableClientId(\n clientId: OAuthClientIdDiscoverable,\n): URL {\n try {\n const url = parseOAuthDiscoverableClientId(clientId)\n\n // Extra validation, prevent usage of invalid internet domain names.\n if (isLocalHostname(url.hostname)) {\n throw new InvalidClientIdError(\n \"The client_id's TLD must not be a local hostname\",\n )\n }\n\n return url\n } catch (err) {\n throw InvalidClientIdError.from(\n err,\n 'Invalid discoverable client identifier',\n )\n }\n}\n"]}
1
+ {"version":3,"file":"client-utils.js","sourceRoot":"","sources":["../../src/client/client-utils.ts"],"names":[],"mappings":";;AAQA,4CAMC;AAED,8DAoBC;AApCD,sDAI6B;AAC7B,qFAA2E;AAC3E,2FAAiF;AAEjF,SAAgB,gBAAgB,CAAC,WAAmB;IAClD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,WAAW,CAAC,CAAA;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,uDAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;AACH,CAAC;AAED,SAAgB,yBAAyB,CACvC,QAAmC;IAEnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,4CAA8B,EAAC,QAAQ,CAAC,CAAA;QAEpD,oEAAoE;QACpE,IAAI,IAAA,6BAAe,EAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,iDAAoB,CAC5B,kDAAkD,CACnD,CAAA;QACH,CAAC;QAED,OAAO,GAAG,CAAA;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,iDAAoB,CAAC,IAAI,CAC7B,GAAG,EACH,wCAAwC,CACzC,CAAA;IACH,CAAC;AACH,CAAC","sourcesContent":["import {\n OAuthClientIdDiscoverable,\n isLocalHostname,\n parseOAuthDiscoverableClientId,\n} from '@atproto/oauth-types'\nimport { InvalidClientIdError } from '../errors/invalid-client-id-error.js'\nimport { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'\n\nexport function parseRedirectUri(redirectUri: string): URL {\n try {\n return new URL(redirectUri)\n } catch (err) {\n throw InvalidRedirectUriError.from(err)\n }\n}\n\nexport function parseDiscoverableClientId(\n clientId: OAuthClientIdDiscoverable,\n): URL {\n try {\n const url = parseOAuthDiscoverableClientId(clientId)\n\n // Extra validation, prevent usage of invalid internet domain names.\n if (isLocalHostname(url.hostname)) {\n throw new InvalidClientIdError(\n \"The client_id's TLD must not be a local hostname\",\n )\n }\n\n return url\n } catch (err) {\n throw InvalidClientIdError.from(\n err,\n 'Invalid discoverable client identifier',\n )\n }\n}\n"]}
@@ -81,7 +81,8 @@ function buildMetadata(issuer, keyset, customMetadata) {
81
81
  token_endpoint_auth_methods_supported: [...client_js_1.Client.AUTH_METHODS_SUPPORTED],
82
82
  token_endpoint_auth_signing_alg_values_supported: [...crypto_js_1.VERIFY_ALGOS],
83
83
  revocation_endpoint: new URL('/oauth/revoke', issuer).href,
84
- introspection_endpoint: new URL('/oauth/introspect', issuer).href,
84
+ // @TODO Should we implement these endpoints?
85
+ // introspection_endpoint: new URL('/oauth/introspect', issuer).href,
85
86
  // end_session_endpoint: new URL('/oauth/logout', issuer).href,
86
87
  // https://datatracker.ietf.org/doc/html/rfc9126#section-5
87
88
  pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href,
@@ -90,9 +91,9 @@ function buildMetadata(issuer, keyset, customMetadata) {
90
91
  dpop_signing_alg_values_supported: [...crypto_js_1.VERIFY_ALGOS],
91
92
  // https://datatracker.ietf.org/doc/html/rfc9396#section-14.4
92
93
  authorization_details_types_supported: customMetadata?.authorization_details_types_supported,
93
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
94
+ // https://www.rfc-editor.org/rfc/rfc9728.html#section-4
94
95
  protected_resources: customMetadata?.protected_resources,
95
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
96
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
96
97
  client_id_metadata_document_supported: true,
97
98
  });
98
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build-metadata.js","sourceRoot":"","sources":["../../src/metadata/build-metadata.ts"],"names":[],"mappings":";;AAkBA,sCAgHC;AAjID,sDAI6B;AAC7B,mDAA4C;AAC5C,qDAAoD;AAOpD;;;GAGG;AACH,SAAgB,aAAa,CAC3B,MAA6B,EAC7B,MAAc,EACd,cAA+B;IAE/B,OAAO,uDAAyC,CAAC,KAAK,CAAC;QACrD,MAAM;QAEN,gBAAgB,EAAE;YAChB,SAAS;YAET,yEAAyE;YACzE,kCAAkC;YAClC,kBAAkB;YAClB,oBAAoB;YACpB,sBAAsB;YAEtB,gEAAgE;SACjE;QACD,uBAAuB,EAAE;YACvB,EAAE;YACF,QAAQ,EAAE,6CAA6C;YACvD,+DAA+D;SAChE;QACD,wBAAwB,EAAE;YACxB,QAAQ;YACR,MAAM;YACN,WAAW;YAEX,SAAS;YACT,UAAU;YACV,yBAAyB;YACzB,mBAAmB;YACnB,gBAAgB;YAChB,oBAAoB;YACpB,cAAc;SACf;QACD,wBAAwB,EAAE;YACxB,mFAAmF;YACnF,OAAO;YACP,UAAU;YACV,0FAA0F;YAC1F,WAAW;SACZ;QACD,qBAAqB,EAAE;YACrB,EAAE;YACF,oBAAoB;YACpB,eAAe;SAChB;QACD,gCAAgC,EAAE;YAChC,sGAAsG;YACtG,MAAM;YAEN,iCAAiC;YACjC,WAAW;SACZ;QACD,oBAAoB,EAAE;YACpB,EAAE;YACF,OAAO;SACR;QACD,wBAAwB,EAAE;YACxB,EAAE;YACF,MAAM;YACN,OAAO;YACP,OAAO;YACP,aAAa;SACd;QAED,gDAAgD;QAChD,8CAA8C,EAAE,IAAI;QAEpD,0DAA0D;QAC1D,2CAA2C,EAAE,CAAC,GAAG,wBAAY,EAAE,MAAM,CAAC;QACtE,8CAA8C,EAAE,EAAE,EAAE,OAAO;QAC3D,8CAA8C,EAAE,EAAE,EAAE,OAAO;QAE3D,2BAA2B,EAAE,IAAI;QACjC,+BAA+B,EAAE,IAAI;QACrC,gCAAgC,EAAE,IAAI;QAEtC,QAAQ,EAAE,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,IAAI;QAE7C,sBAAsB,EAAE,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,IAAI;QAEhE,cAAc,EAAE,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI;QACpD,qCAAqC,EAAE,CAAC,GAAG,kBAAM,CAAC,sBAAsB,CAAC;QACzE,gDAAgD,EAAE,CAAC,GAAG,wBAAY,CAAC;QAEnE,mBAAmB,EAAE,IAAI,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,IAAI;QAE1D,sBAAsB,EAAE,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,IAAI;QAEjE,+DAA+D;QAE/D,0DAA0D;QAC1D,qCAAqC,EAAE,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,IAAI;QAEzE,qCAAqC,EAAE,IAAI;QAE3C,4DAA4D;QAC5D,iCAAiC,EAAE,CAAC,GAAG,wBAAY,CAAC;QAEpD,6DAA6D;QAC7D,qCAAqC,EACnC,cAAc,EAAE,qCAAqC;QAEvD,wFAAwF;QACxF,mBAAmB,EAAE,cAAc,EAAE,mBAAmB;QAExD,kIAAkI;QAClI,qCAAqC,EAAE,IAAI;KAC5C,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Keyset } from '@atproto/jwk'\nimport {\n OAuthAuthorizationServerMetadata,\n OAuthIssuerIdentifier,\n oauthAuthorizationServerMetadataValidator,\n} from '@atproto/oauth-types'\nimport { Client } from '../client/client.js'\nimport { VERIFY_ALGOS } from '../lib/util/crypto.js'\n\nexport type CustomMetadata = {\n authorization_details_types_supported?: string[]\n protected_resources?: string[]\n}\n\n/**\n * @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2}\n * @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata}\n */\nexport function buildMetadata(\n issuer: OAuthIssuerIdentifier,\n keyset: Keyset,\n customMetadata?: CustomMetadata,\n): OAuthAuthorizationServerMetadata {\n return oauthAuthorizationServerMetadataValidator.parse({\n issuer,\n\n scopes_supported: [\n 'atproto',\n\n // These serve as hint that this server supports the transitional scopes.\n // This is not a specced behavior.\n 'transition:email',\n 'transition:generic',\n 'transition:chat.bsky',\n\n // Other atproto scopes can't be enumerated as they are dynamic.\n ],\n subject_types_supported: [\n //\n 'public', // The same \"sub\" is returned for all clients\n // 'pairwise', // A different \"sub\" is returned for each client\n ],\n response_types_supported: [\n // OAuth\n 'code',\n // 'token',\n\n // OpenID\n // 'none',\n // 'code id_token token',\n // 'code id_token',\n // 'code token',\n // 'id_token token',\n // 'id_token',\n ],\n response_modes_supported: [\n // https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes\n 'query',\n 'fragment',\n // https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode\n 'form_post',\n ],\n grant_types_supported: [\n //\n 'authorization_code',\n 'refresh_token',\n ],\n code_challenge_methods_supported: [\n // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method\n 'S256',\n\n // atproto does not allow \"plain\"\n // 'plain',\n ],\n ui_locales_supported: [\n //\n 'en-US',\n ],\n display_values_supported: [\n //\n 'page',\n 'popup',\n 'touch',\n // 'wap', LoL\n ],\n\n // https://datatracker.ietf.org/doc/html/rfc9207\n authorization_response_iss_parameter_supported: true,\n\n // https://datatracker.ietf.org/doc/html/rfc9101#section-4\n request_object_signing_alg_values_supported: [...VERIFY_ALGOS, 'none'],\n request_object_encryption_alg_values_supported: [], // None\n request_object_encryption_enc_values_supported: [], // None\n\n request_parameter_supported: true,\n request_uri_parameter_supported: true,\n require_request_uri_registration: true,\n\n jwks_uri: new URL('/oauth/jwks', issuer).href,\n\n authorization_endpoint: new URL('/oauth/authorize', issuer).href,\n\n token_endpoint: new URL('/oauth/token', issuer).href,\n token_endpoint_auth_methods_supported: [...Client.AUTH_METHODS_SUPPORTED],\n token_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],\n\n revocation_endpoint: new URL('/oauth/revoke', issuer).href,\n\n introspection_endpoint: new URL('/oauth/introspect', issuer).href,\n\n // end_session_endpoint: new URL('/oauth/logout', issuer).href,\n\n // https://datatracker.ietf.org/doc/html/rfc9126#section-5\n pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href,\n\n require_pushed_authorization_requests: true,\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1\n dpop_signing_alg_values_supported: [...VERIFY_ALGOS],\n\n // https://datatracker.ietf.org/doc/html/rfc9396#section-14.4\n authorization_details_types_supported:\n customMetadata?.authorization_details_types_supported,\n\n // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4\n protected_resources: customMetadata?.protected_resources,\n\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html\n client_id_metadata_document_supported: true,\n })\n}\n"]}
1
+ {"version":3,"file":"build-metadata.js","sourceRoot":"","sources":["../../src/metadata/build-metadata.ts"],"names":[],"mappings":";;AAkBA,sCAgHC;AAjID,sDAI6B;AAC7B,mDAA4C;AAC5C,qDAAoD;AAOpD;;;GAGG;AACH,SAAgB,aAAa,CAC3B,MAA6B,EAC7B,MAAc,EACd,cAA+B;IAE/B,OAAO,uDAAyC,CAAC,KAAK,CAAC;QACrD,MAAM;QAEN,gBAAgB,EAAE;YAChB,SAAS;YAET,yEAAyE;YACzE,kCAAkC;YAClC,kBAAkB;YAClB,oBAAoB;YACpB,sBAAsB;YAEtB,gEAAgE;SACjE;QACD,uBAAuB,EAAE;YACvB,EAAE;YACF,QAAQ,EAAE,6CAA6C;YACvD,+DAA+D;SAChE;QACD,wBAAwB,EAAE;YACxB,QAAQ;YACR,MAAM;YACN,WAAW;YAEX,SAAS;YACT,UAAU;YACV,yBAAyB;YACzB,mBAAmB;YACnB,gBAAgB;YAChB,oBAAoB;YACpB,cAAc;SACf;QACD,wBAAwB,EAAE;YACxB,mFAAmF;YACnF,OAAO;YACP,UAAU;YACV,0FAA0F;YAC1F,WAAW;SACZ;QACD,qBAAqB,EAAE;YACrB,EAAE;YACF,oBAAoB;YACpB,eAAe;SAChB;QACD,gCAAgC,EAAE;YAChC,sGAAsG;YACtG,MAAM;YAEN,iCAAiC;YACjC,WAAW;SACZ;QACD,oBAAoB,EAAE;YACpB,EAAE;YACF,OAAO;SACR;QACD,wBAAwB,EAAE;YACxB,EAAE;YACF,MAAM;YACN,OAAO;YACP,OAAO;YACP,aAAa;SACd;QAED,gDAAgD;QAChD,8CAA8C,EAAE,IAAI;QAEpD,0DAA0D;QAC1D,2CAA2C,EAAE,CAAC,GAAG,wBAAY,EAAE,MAAM,CAAC;QACtE,8CAA8C,EAAE,EAAE,EAAE,OAAO;QAC3D,8CAA8C,EAAE,EAAE,EAAE,OAAO;QAE3D,2BAA2B,EAAE,IAAI;QACjC,+BAA+B,EAAE,IAAI;QACrC,gCAAgC,EAAE,IAAI;QAEtC,QAAQ,EAAE,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,IAAI;QAE7C,sBAAsB,EAAE,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,IAAI;QAEhE,cAAc,EAAE,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI;QACpD,qCAAqC,EAAE,CAAC,GAAG,kBAAM,CAAC,sBAAsB,CAAC;QACzE,gDAAgD,EAAE,CAAC,GAAG,wBAAY,CAAC;QAEnE,mBAAmB,EAAE,IAAI,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,IAAI;QAE1D,6CAA6C;QAC7C,qEAAqE;QACrE,+DAA+D;QAE/D,0DAA0D;QAC1D,qCAAqC,EAAE,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,IAAI;QAEzE,qCAAqC,EAAE,IAAI;QAE3C,4DAA4D;QAC5D,iCAAiC,EAAE,CAAC,GAAG,wBAAY,CAAC;QAEpD,6DAA6D;QAC7D,qCAAqC,EACnC,cAAc,EAAE,qCAAqC;QAEvD,wDAAwD;QACxD,mBAAmB,EAAE,cAAc,EAAE,mBAAmB;QAExD,uFAAuF;QACvF,qCAAqC,EAAE,IAAI;KAC5C,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Keyset } from '@atproto/jwk'\nimport {\n OAuthAuthorizationServerMetadata,\n OAuthIssuerIdentifier,\n oauthAuthorizationServerMetadataValidator,\n} from '@atproto/oauth-types'\nimport { Client } from '../client/client.js'\nimport { VERIFY_ALGOS } from '../lib/util/crypto.js'\n\nexport type CustomMetadata = {\n authorization_details_types_supported?: string[]\n protected_resources?: string[]\n}\n\n/**\n * @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2}\n * @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata}\n */\nexport function buildMetadata(\n issuer: OAuthIssuerIdentifier,\n keyset: Keyset,\n customMetadata?: CustomMetadata,\n): OAuthAuthorizationServerMetadata {\n return oauthAuthorizationServerMetadataValidator.parse({\n issuer,\n\n scopes_supported: [\n 'atproto',\n\n // These serve as hint that this server supports the transitional scopes.\n // This is not a specced behavior.\n 'transition:email',\n 'transition:generic',\n 'transition:chat.bsky',\n\n // Other atproto scopes can't be enumerated as they are dynamic.\n ],\n subject_types_supported: [\n //\n 'public', // The same \"sub\" is returned for all clients\n // 'pairwise', // A different \"sub\" is returned for each client\n ],\n response_types_supported: [\n // OAuth\n 'code',\n // 'token',\n\n // OpenID\n // 'none',\n // 'code id_token token',\n // 'code id_token',\n // 'code token',\n // 'id_token token',\n // 'id_token',\n ],\n response_modes_supported: [\n // https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes\n 'query',\n 'fragment',\n // https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode\n 'form_post',\n ],\n grant_types_supported: [\n //\n 'authorization_code',\n 'refresh_token',\n ],\n code_challenge_methods_supported: [\n // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method\n 'S256',\n\n // atproto does not allow \"plain\"\n // 'plain',\n ],\n ui_locales_supported: [\n //\n 'en-US',\n ],\n display_values_supported: [\n //\n 'page',\n 'popup',\n 'touch',\n // 'wap', LoL\n ],\n\n // https://datatracker.ietf.org/doc/html/rfc9207\n authorization_response_iss_parameter_supported: true,\n\n // https://datatracker.ietf.org/doc/html/rfc9101#section-4\n request_object_signing_alg_values_supported: [...VERIFY_ALGOS, 'none'],\n request_object_encryption_alg_values_supported: [], // None\n request_object_encryption_enc_values_supported: [], // None\n\n request_parameter_supported: true,\n request_uri_parameter_supported: true,\n require_request_uri_registration: true,\n\n jwks_uri: new URL('/oauth/jwks', issuer).href,\n\n authorization_endpoint: new URL('/oauth/authorize', issuer).href,\n\n token_endpoint: new URL('/oauth/token', issuer).href,\n token_endpoint_auth_methods_supported: [...Client.AUTH_METHODS_SUPPORTED],\n token_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],\n\n revocation_endpoint: new URL('/oauth/revoke', issuer).href,\n\n // @TODO Should we implement these endpoints?\n // introspection_endpoint: new URL('/oauth/introspect', issuer).href,\n // end_session_endpoint: new URL('/oauth/logout', issuer).href,\n\n // https://datatracker.ietf.org/doc/html/rfc9126#section-5\n pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href,\n\n require_pushed_authorization_requests: true,\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1\n dpop_signing_alg_values_supported: [...VERIFY_ALGOS],\n\n // https://datatracker.ietf.org/doc/html/rfc9396#section-14.4\n authorization_details_types_supported:\n customMetadata?.authorization_details_types_supported,\n\n // https://www.rfc-editor.org/rfc/rfc9728.html#section-4\n protected_resources: customMetadata?.protected_resources,\n\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html\n client_id_metadata_document_supported: true,\n })\n}\n"]}
@@ -62,6 +62,16 @@ export type OAuthHooks = {
62
62
  deviceId: DeviceId;
63
63
  deviceMetadata: RequestMetadata;
64
64
  }) => Awaitable<void>;
65
+ /**
66
+ * This hook is called when a user requests a password reset, before the
67
+ * reset password request is triggered on the account store.
68
+ */
69
+ onResetPasswordRequested?: (data: {
70
+ input: ResetPasswordRequestInput;
71
+ deviceId: DeviceId;
72
+ deviceMetadata: RequestMetadata;
73
+ account: Account;
74
+ }) => Awaitable<void>;
65
75
  /**
66
76
  * This hook is called when a user confirms a password reset, before the
67
77
  * password is actually reset on the account store.
@@ -71,6 +81,16 @@ export type OAuthHooks = {
71
81
  deviceId: DeviceId;
72
82
  deviceMetadata: RequestMetadata;
73
83
  }) => Awaitable<void>;
84
+ /**
85
+ * This hook is called after a user confirms a password reset, and the
86
+ * password was successfully reset on the account store.
87
+ */
88
+ onResetPasswordConfirmed?: (data: {
89
+ input: ResetPasswordConfirmInput;
90
+ deviceId: DeviceId;
91
+ deviceMetadata: RequestMetadata;
92
+ account: Account;
93
+ }) => Awaitable<void>;
74
94
  /**
75
95
  * This hook is called when a user successfully signs up.
76
96
  *
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-hooks.d.ts","sourceRoot":"","sources":["../src/oauth-hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"oauth-hooks.d.ts","sourceRoot":"","sources":["../src/oauth-hooks.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-hooks.js","sourceRoot":"","sources":["../src/oauth-hooks.ts"],"names":[],"mappings":";;;AAoBA,kDAA2C;AAyBzC,uFAzBO,kBAAM,OAyBP;AAtBR,4EAAmE;AAiBjE,kGAjBO,0CAAiB,OAiBP;AAhBnB,4EAAoE;AAmBlE,mGAnBO,2CAAkB,OAmBP;AAlBpB,gFAAuE;AA6BrE,oGA7BO,8CAAmB,OA6BP;AA5BrB,4DAAoD;AAkClD,2FAlCO,2BAAU,OAkCP","sourcesContent":["import { Jwks } from '@atproto/jwk'\nimport type { Account } from '@atproto/oauth-provider-api'\nimport {\n OAuthAccessToken,\n OAuthAuthorizationDetails,\n OAuthAuthorizationRequestParameters,\n OAuthClientMetadata,\n OAuthTokenResponse,\n OAuthTokenType,\n} from '@atproto/oauth-types'\nimport {\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account/account-store.js'\nimport { SignInData } from './account/sign-in-data.js'\nimport { SignUpInput } from './account/sign-up-input.js'\nimport { ClientAuth } from './client/client-auth.js'\nimport { ClientId } from './client/client-id.js'\nimport { ClientInfo } from './client/client-info.js'\nimport { Client } from './client/client.js'\nimport { DeviceId } from './device/device-id.js'\nimport { DpopProof } from './dpop/dpop-proof.js'\nimport { AccessDeniedError } from './errors/access-denied-error.js'\nimport { AuthorizationError } from './errors/authorization-error.js'\nimport { InvalidRequestError } from './errors/invalid-request-error.js'\nimport { OAuthError } from './errors/oauth-error.js'\nimport {\n HcaptchaClientTokens,\n HcaptchaConfig,\n HcaptchaVerifyResult,\n} from './lib/hcaptcha.js'\nimport { RequestMetadata } from './lib/http/request.js'\nimport { Awaitable, OmitKey } from './lib/util/type.js'\nimport { RequestId } from './request/request-id.js'\nimport { AccessTokenPayload } from './signer/access-token-payload.js'\nimport { TokenClaims } from './token/token-claims.js'\n\n// Make sure all types needed to implement the OAuthHooks are exported\nexport {\n AccessDeniedError,\n type AccessTokenPayload,\n type Account,\n AuthorizationError,\n type Awaitable,\n Client,\n type ClientAuth,\n type ClientId,\n type ClientInfo,\n type DeviceId,\n type DpopProof,\n type HcaptchaClientTokens,\n type HcaptchaConfig,\n type HcaptchaVerifyResult,\n InvalidRequestError,\n type Jwks,\n type OAuthAccessToken,\n type OAuthAuthorizationDetails,\n type OAuthAuthorizationRequestParameters,\n type OAuthClientMetadata,\n OAuthError,\n type OAuthTokenResponse,\n type OAuthTokenType,\n type RequestMetadata,\n type ResetPasswordConfirmInput,\n type ResetPasswordRequestInput,\n type SignInData,\n type SignUpData,\n type SignUpInput,\n type TokenClaims,\n}\n\nexport type OAuthHooks = {\n /**\n * Use this to alter, override or validate the client metadata & jwks returned\n * by the client store.\n *\n * @throws {InvalidClientMetadataError} if the metadata is invalid\n * @see {@link InvalidClientMetadataError}\n */\n getClientInfo?: (\n clientId: ClientId,\n data: { metadata: OAuthClientMetadata; jwks?: Jwks },\n ) => Awaitable<undefined | Partial<ClientInfo>>\n\n /**\n * This hook is called when a user attempts to sign up, after every validation\n * has passed (including hcaptcha).\n */\n onSignUpAttempt?: (data: {\n input: SignUpInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user attempts to sign up, after the hcaptcha\n * `/siteverify` request has been made (and before the result is validated).\n */\n onHcaptchaResult?: (data: {\n input: SignUpInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n tokens: HcaptchaClientTokens\n result: HcaptchaVerifyResult\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user requests a password reset, before the\n * reset password request is triggered on the account store.\n */\n onResetPasswordRequest?: (data: {\n input: ResetPasswordRequestInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user confirms a password reset, before the\n * password is actually reset on the account store.\n */\n onResetPasswordConfirm?: (data: {\n input: ResetPasswordConfirmInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user successfully signs up.\n *\n * @throws {AccessDeniedError} to deny the sign-up\n */\n onSignedUp?: (data: {\n data: SignUpData\n account: Account\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n onSignInAttempt?: (data: {\n data: SignInData\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user successfully signs in.\n *\n * @throws {InvalidRequestError} when the sing-in should be denied\n */\n onSignedIn?: (data: {\n data: SignInData\n account: Account\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * Allows validating an authorization request (typically the requested scopes)\n * before it is created. Note that the validity against the client metadata is\n * already enforced by the OAuth provider.\n *\n * @throws {AuthorizationError}\n */\n onAuthorizationRequest?: (data: {\n client: Client\n clientAuth: null | ClientAuth\n parameters: Readonly<OAuthAuthorizationRequestParameters>\n }) => Awaitable<void>\n\n /**\n * This hook is called when a client is authorized.\n *\n * @throws {AuthorizationError} to deny the authorization request and redirect\n * the user to the client with an OAuth error (other errors will result in an\n * internal server error being displayed to the user)\n *\n * @note We use `deviceMetadata` instead of `clientMetadata` to make it clear\n * that this metadata is from the user device, which might be different from\n * the client metadata (because the OAuth client could live in a backend).\n */\n onAuthorized?: (data: {\n client: Client\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n requestId: RequestId\n }) => Awaitable<void>\n\n /**\n * This hook is called whenever a token is about to be created. You can use\n * it to modify the token claims or perform additional validation.\n *\n * This hook should never throw an error.\n */\n onCreateToken?: (data: {\n client: Client\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n claims: TokenClaims\n }) => Awaitable<void | OmitKey<AccessTokenPayload, 'iss'>>\n\n /**\n * This hook is called whenever a token was just decoded, and basic validation\n * was performed (signature, expiration, not-before).\n *\n * It can be used to modify the payload (e.g., to add custom claims), or to\n * perform additional validation.\n *\n * This hook is called when authenticating requests through the\n * `authenticateRequest()` method in `OAuthVerifier` and `OAuthProvider`.\n *\n * Any error thrown here will be propagated.\n */\n onDecodeToken?: (data: {\n tokenType: OAuthTokenType\n token: OAuthAccessToken\n payload: AccessTokenPayload\n dpopProof: null | DpopProof\n }) => Promise<AccessTokenPayload | void>\n\n /**\n * This hook is called when an authorized client exchanges an authorization\n * code for an access token.\n *\n * @throws {OAuthError} to cancel the token creation and revoke the session\n */\n onTokenCreated?: (data: {\n client: Client\n clientAuth: ClientAuth\n clientMetadata: RequestMetadata\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n }) => Awaitable<void>\n\n /**\n * This hook is called when an authorized client refreshes an access token.\n *\n * @throws {OAuthError} to cancel the token refresh and revoke the session\n */\n onTokenRefreshed?: (data: {\n client: Client\n clientAuth: ClientAuth\n clientMetadata: RequestMetadata\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n }) => Awaitable<void>\n}\n"]}
1
+ {"version":3,"file":"oauth-hooks.js","sourceRoot":"","sources":["../src/oauth-hooks.ts"],"names":[],"mappings":";;;AAoBA,kDAA2C;AAyBzC,uFAzBO,kBAAM,OAyBP;AAtBR,4EAAmE;AAiBjE,kGAjBO,0CAAiB,OAiBP;AAhBnB,4EAAoE;AAmBlE,mGAnBO,2CAAkB,OAmBP;AAlBpB,gFAAuE;AA6BrE,oGA7BO,8CAAmB,OA6BP;AA5BrB,4DAAoD;AAkClD,2FAlCO,2BAAU,OAkCP","sourcesContent":["import { Jwks } from '@atproto/jwk'\nimport type { Account } from '@atproto/oauth-provider-api'\nimport {\n OAuthAccessToken,\n OAuthAuthorizationDetails,\n OAuthAuthorizationRequestParameters,\n OAuthClientMetadata,\n OAuthTokenResponse,\n OAuthTokenType,\n} from '@atproto/oauth-types'\nimport {\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account/account-store.js'\nimport { SignInData } from './account/sign-in-data.js'\nimport { SignUpInput } from './account/sign-up-input.js'\nimport { ClientAuth } from './client/client-auth.js'\nimport { ClientId } from './client/client-id.js'\nimport { ClientInfo } from './client/client-info.js'\nimport { Client } from './client/client.js'\nimport { DeviceId } from './device/device-id.js'\nimport { DpopProof } from './dpop/dpop-proof.js'\nimport { AccessDeniedError } from './errors/access-denied-error.js'\nimport { AuthorizationError } from './errors/authorization-error.js'\nimport { InvalidRequestError } from './errors/invalid-request-error.js'\nimport { OAuthError } from './errors/oauth-error.js'\nimport {\n HcaptchaClientTokens,\n HcaptchaConfig,\n HcaptchaVerifyResult,\n} from './lib/hcaptcha.js'\nimport { RequestMetadata } from './lib/http/request.js'\nimport { Awaitable, OmitKey } from './lib/util/type.js'\nimport { RequestId } from './request/request-id.js'\nimport { AccessTokenPayload } from './signer/access-token-payload.js'\nimport { TokenClaims } from './token/token-claims.js'\n\n// Make sure all types needed to implement the OAuthHooks are exported\nexport {\n AccessDeniedError,\n type AccessTokenPayload,\n type Account,\n AuthorizationError,\n type Awaitable,\n Client,\n type ClientAuth,\n type ClientId,\n type ClientInfo,\n type DeviceId,\n type DpopProof,\n type HcaptchaClientTokens,\n type HcaptchaConfig,\n type HcaptchaVerifyResult,\n InvalidRequestError,\n type Jwks,\n type OAuthAccessToken,\n type OAuthAuthorizationDetails,\n type OAuthAuthorizationRequestParameters,\n type OAuthClientMetadata,\n OAuthError,\n type OAuthTokenResponse,\n type OAuthTokenType,\n type RequestMetadata,\n type ResetPasswordConfirmInput,\n type ResetPasswordRequestInput,\n type SignInData,\n type SignUpData,\n type SignUpInput,\n type TokenClaims,\n}\n\nexport type OAuthHooks = {\n /**\n * Use this to alter, override or validate the client metadata & jwks returned\n * by the client store.\n *\n * @throws {InvalidClientMetadataError} if the metadata is invalid\n * @see {@link InvalidClientMetadataError}\n */\n getClientInfo?: (\n clientId: ClientId,\n data: { metadata: OAuthClientMetadata; jwks?: Jwks },\n ) => Awaitable<undefined | Partial<ClientInfo>>\n\n /**\n * This hook is called when a user attempts to sign up, after every validation\n * has passed (including hcaptcha).\n */\n onSignUpAttempt?: (data: {\n input: SignUpInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user attempts to sign up, after the hcaptcha\n * `/siteverify` request has been made (and before the result is validated).\n */\n onHcaptchaResult?: (data: {\n input: SignUpInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n tokens: HcaptchaClientTokens\n result: HcaptchaVerifyResult\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user requests a password reset, before the\n * reset password request is triggered on the account store.\n */\n onResetPasswordRequest?: (data: {\n input: ResetPasswordRequestInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user requests a password reset, before the\n * reset password request is triggered on the account store.\n */\n onResetPasswordRequested?: (data: {\n input: ResetPasswordRequestInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n account: Account\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user confirms a password reset, before the\n * password is actually reset on the account store.\n */\n onResetPasswordConfirm?: (data: {\n input: ResetPasswordConfirmInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called after a user confirms a password reset, and the\n * password was successfully reset on the account store.\n */\n onResetPasswordConfirmed?: (data: {\n input: ResetPasswordConfirmInput\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n account: Account\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user successfully signs up.\n *\n * @throws {AccessDeniedError} to deny the sign-up\n */\n onSignedUp?: (data: {\n data: SignUpData\n account: Account\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n onSignInAttempt?: (data: {\n data: SignInData\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * This hook is called when a user successfully signs in.\n *\n * @throws {InvalidRequestError} when the sing-in should be denied\n */\n onSignedIn?: (data: {\n data: SignInData\n account: Account\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n }) => Awaitable<void>\n\n /**\n * Allows validating an authorization request (typically the requested scopes)\n * before it is created. Note that the validity against the client metadata is\n * already enforced by the OAuth provider.\n *\n * @throws {AuthorizationError}\n */\n onAuthorizationRequest?: (data: {\n client: Client\n clientAuth: null | ClientAuth\n parameters: Readonly<OAuthAuthorizationRequestParameters>\n }) => Awaitable<void>\n\n /**\n * This hook is called when a client is authorized.\n *\n * @throws {AuthorizationError} to deny the authorization request and redirect\n * the user to the client with an OAuth error (other errors will result in an\n * internal server error being displayed to the user)\n *\n * @note We use `deviceMetadata` instead of `clientMetadata` to make it clear\n * that this metadata is from the user device, which might be different from\n * the client metadata (because the OAuth client could live in a backend).\n */\n onAuthorized?: (data: {\n client: Client\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n deviceId: DeviceId\n deviceMetadata: RequestMetadata\n requestId: RequestId\n }) => Awaitable<void>\n\n /**\n * This hook is called whenever a token is about to be created. You can use\n * it to modify the token claims or perform additional validation.\n *\n * This hook should never throw an error.\n */\n onCreateToken?: (data: {\n client: Client\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n claims: TokenClaims\n }) => Awaitable<void | OmitKey<AccessTokenPayload, 'iss'>>\n\n /**\n * This hook is called whenever a token was just decoded, and basic validation\n * was performed (signature, expiration, not-before).\n *\n * It can be used to modify the payload (e.g., to add custom claims), or to\n * perform additional validation.\n *\n * This hook is called when authenticating requests through the\n * `authenticateRequest()` method in `OAuthVerifier` and `OAuthProvider`.\n *\n * Any error thrown here will be propagated.\n */\n onDecodeToken?: (data: {\n tokenType: OAuthTokenType\n token: OAuthAccessToken\n payload: AccessTokenPayload\n dpopProof: null | DpopProof\n }) => Promise<AccessTokenPayload | void>\n\n /**\n * This hook is called when an authorized client exchanges an authorization\n * code for an access token.\n *\n * @throws {OAuthError} to cancel the token creation and revoke the session\n */\n onTokenCreated?: (data: {\n client: Client\n clientAuth: ClientAuth\n clientMetadata: RequestMetadata\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n }) => Awaitable<void>\n\n /**\n * This hook is called when an authorized client refreshes an access token.\n *\n * @throws {OAuthError} to cancel the token refresh and revoke the session\n */\n onTokenRefreshed?: (data: {\n client: Client\n clientAuth: ClientAuth\n clientMetadata: RequestMetadata\n account: Account\n parameters: OAuthAuthorizationRequestParameters\n }) => Awaitable<void>\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-provider",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "license": "MIT",
5
5
  "description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.",
6
6
  "keywords": [
@@ -43,7 +43,7 @@
43
43
  "jose": "^5.2.0",
44
44
  "zod": "^3.23.8",
45
45
  "@atproto-labs/fetch": "0.2.3",
46
- "@atproto-labs/fetch-node": "0.1.10",
46
+ "@atproto-labs/fetch-node": "0.2.0",
47
47
  "@atproto-labs/pipe": "0.1.1",
48
48
  "@atproto-labs/simple-store": "0.3.0",
49
49
  "@atproto-labs/simple-store-memory": "0.1.4",
@@ -52,11 +52,11 @@
52
52
  "@atproto/jwk": "0.6.0",
53
53
  "@atproto/jwk-jose": "0.1.11",
54
54
  "@atproto/lexicon": "0.5.1",
55
- "@atproto/lexicon-resolver": "0.2.2",
56
- "@atproto/oauth-types": "0.4.2",
57
- "@atproto/oauth-provider-api": "0.3.1",
58
- "@atproto/oauth-provider-frontend": "0.2.2",
59
- "@atproto/oauth-provider-ui": "0.3.2",
55
+ "@atproto/lexicon-resolver": "0.2.3",
56
+ "@atproto/oauth-types": "0.5.0",
57
+ "@atproto/oauth-provider-api": "0.3.2",
58
+ "@atproto/oauth-provider-frontend": "0.2.3",
59
+ "@atproto/oauth-provider-ui": "0.3.3",
60
60
  "@atproto/oauth-scopes": "0.2.1",
61
61
  "@atproto/syntax": "0.4.1"
62
62
  },
@@ -258,7 +258,18 @@ export class AccountManager {
258
258
  })
259
259
 
260
260
  return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {
261
- await this.store.resetPasswordRequest(input)
261
+ const account = await this.store.resetPasswordRequest(input)
262
+
263
+ if (!account) {
264
+ return // Silently ignore to prevent user enumeration
265
+ }
266
+
267
+ await this.hooks.onResetPasswordRequested?.call(null, {
268
+ input,
269
+ deviceId,
270
+ deviceMetadata,
271
+ account,
272
+ })
262
273
  })
263
274
  }
264
275
 
@@ -274,7 +285,18 @@ export class AccountManager {
274
285
  })
275
286
 
276
287
  return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {
277
- await this.store.resetPasswordConfirm(input)
288
+ const account = await this.store.resetPasswordConfirm(input)
289
+
290
+ if (!account) {
291
+ throw new InvalidRequestError('Invalid token')
292
+ }
293
+
294
+ await this.hooks.onResetPasswordConfirmed?.call(null, {
295
+ input,
296
+ deviceId,
297
+ deviceMetadata,
298
+ account,
299
+ })
278
300
  })
279
301
  }
280
302
 
@@ -171,8 +171,13 @@ export interface AccountStore {
171
171
  filter: { sub: Sub } | { deviceId: DeviceId },
172
172
  ): Awaitable<DeviceAccount[]>
173
173
 
174
- resetPasswordRequest(data: ResetPasswordRequestInput): Awaitable<void>
175
- resetPasswordConfirm(data: ResetPasswordConfirmInput): Awaitable<void>
174
+ resetPasswordRequest(
175
+ data: ResetPasswordRequestInput,
176
+ ): Awaitable<null | Account>
177
+
178
+ resetPasswordConfirm(
179
+ data: ResetPasswordConfirmInput,
180
+ ): Awaitable<null | Account>
176
181
 
177
182
  /**
178
183
  * @throws {HandleUnavailableError} - To indicate that the handle is already taken
@@ -5,7 +5,7 @@ import {
5
5
  OAuthClientIdLoopback,
6
6
  OAuthClientMetadata,
7
7
  OAuthClientMetadataInput,
8
- isLoopbackHost,
8
+ isLocalHostname,
9
9
  isOAuthClientIdDiscoverable,
10
10
  isOAuthClientIdLoopback,
11
11
  oauthClientMetadataSchema,
@@ -17,7 +17,6 @@ import {
17
17
  fetchJsonZodProcessor,
18
18
  fetchOkProcessor,
19
19
  } from '@atproto-labs/fetch'
20
- import { isLocalHostname } from '@atproto-labs/fetch-node'
21
20
  import { pipe } from '@atproto-labs/pipe'
22
21
  import {
23
22
  CachedGetter,
@@ -36,7 +35,7 @@ import { Client } from './client.js'
36
35
 
37
36
  const fetchMetadataHandler = pipe(
38
37
  fetchOkProcessor(),
39
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1
38
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1
40
39
  fetchJsonProcessor('application/json', true),
41
40
  fetchJsonZodProcessor(oauthClientMetadataSchema),
42
41
  )
@@ -232,6 +231,10 @@ export class ClientManager {
232
231
  clientId: ClientId,
233
232
  metadata: OAuthClientMetadata,
234
233
  ): OAuthClientMetadata {
234
+ // @TODO This method should only check for rules that are specific to this
235
+ // implementation or the ATPROTO specification. All generic validation rules
236
+ // should be moved to the @atproto/oauth-types package.
237
+
235
238
  if (metadata.jwks && metadata.jwks_uri) {
236
239
  throw new InvalidClientMetadataError(
237
240
  'jwks_uri and jwks are mutually exclusive',
@@ -604,56 +607,12 @@ export class ClientManager {
604
607
  }
605
608
 
606
609
  case isPrivateUseUriScheme(url): {
607
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
608
- //
609
- // > When choosing a URI scheme to associate with the app, apps MUST
610
- // > use a URI scheme based on a domain name under their control,
611
- // > expressed in reverse order, as recommended by Section 3.8 of
612
- // > [RFC7595] for private-use URI schemes.
613
-
614
610
  if (metadata.application_type !== 'native') {
615
611
  throw new InvalidRedirectUriError(
616
612
  `Private-Use URI Scheme redirect URI are only allowed for native apps`,
617
613
  )
618
614
  }
619
615
 
620
- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
621
- //
622
- // > In addition to the collision-resistant properties, requiring a
623
- // > URI scheme based on a domain name that is under the control of
624
- // > the app can help to prove ownership in the event of a dispute
625
- // > where two apps claim the same private-use URI scheme (where one
626
- // > app is acting maliciously).
627
- //
628
- // We can't check for ownership here (as there is no concept of
629
- // proven ownership in the generic client validation), but we can
630
- // check that the domain is a valid domain name.
631
-
632
- const urlDomain = reverseDomain(url.protocol.slice(0, -1))
633
-
634
- if (isLocalHostname(urlDomain)) {
635
- throw new InvalidRedirectUriError(
636
- `Private-use URI Scheme redirect URI must not be a local hostname`,
637
- )
638
- }
639
-
640
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
641
- //
642
- // > Following the requirements of Section 3.2 of [RFC3986], as there
643
- // > is no naming authority for private-use URI scheme redirects, only
644
- // > a single slash ("/") appears after the scheme component.
645
- if (
646
- url.href.startsWith(`${url.protocol}//`) ||
647
- url.username ||
648
- url.password ||
649
- url.hostname ||
650
- url.port
651
- ) {
652
- throw new InvalidRedirectUriError(
653
- `Private-Use URI Scheme must be in the form ${url.protocol}/<path>`,
654
- )
655
- }
656
-
657
616
  break
658
617
  }
659
618
 
@@ -700,22 +659,6 @@ export class ClientManager {
700
659
  )
701
660
  }
702
661
 
703
- for (const redirectUri of metadata.redirect_uris) {
704
- const url = parseRedirectUri(redirectUri)
705
-
706
- if (url.protocol !== 'http:') {
707
- throw new InvalidRedirectUriError(
708
- `Loopback clients must use HTTP redirect URIs`,
709
- )
710
- }
711
-
712
- if (!isLoopbackHost(url.hostname)) {
713
- throw new InvalidRedirectUriError(
714
- `Loopback clients must use loopback redirect URIs`,
715
- )
716
- }
717
- }
718
-
719
662
  return metadata
720
663
  }
721
664
 
@@ -724,7 +667,7 @@ export class ClientManager {
724
667
  metadata: OAuthClientMetadata,
725
668
  ): OAuthClientMetadata {
726
669
  if (!metadata.client_id) {
727
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
670
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
728
671
  throw new InvalidClientMetadataError(
729
672
  `client_id is required for discoverable clients`,
730
673
  )
@@ -733,7 +676,7 @@ export class ClientManager {
733
676
  const clientIdUrl = parseDiscoverableClientId(clientId)
734
677
 
735
678
  if (metadata.client_uri) {
736
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
679
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
737
680
  //
738
681
  // The client_uri must be a parent of the client_id URL. This might be
739
682
  // relaxed in the future.
@@ -762,9 +705,19 @@ export class ClientManager {
762
705
  }
763
706
 
764
707
  for (const redirectUri of metadata.redirect_uris) {
708
+ // @NOTE at this point, all redirect URIs have already been validated by
709
+ // oauthRedirectUriSchema
710
+
765
711
  const url = parseRedirectUri(redirectUri)
766
712
 
767
713
  if (isPrivateUseUriScheme(url)) {
714
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
715
+ //
716
+ // > When choosing a URI scheme to associate with the app, apps MUST use
717
+ // > a URI scheme based on a domain name under their control, expressed
718
+ // > in reverse order, as recommended by Section 3.8 of [RFC7595] for
719
+ // > private-use URI schemes.
720
+
768
721
  // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
769
722
  //
770
723
  // > In addition to the collision-resistant properties, requiring a
@@ -773,11 +726,14 @@ export class ClientManager {
773
726
  // > where two apps claim the same private-use URI scheme (where one
774
727
  // > app is acting maliciously).
775
728
 
776
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
729
+ // https://atproto.com/specs/oauth
777
730
  //
778
- // Fully qualified domain name (FQDN) of the client_id, in reverse
779
- // order. This could be relaxed to allow same apex domain names, or
780
- // parent domains, but for now we require an exact match.
731
+ // > Any custom scheme must match the client_id hostname in
732
+ // > reverse-domain order. The URI scheme must be followed by a single
733
+ // > colon (:) then a single forward slash (/) and then a URI path
734
+ // > component. For example, an app with client_id
735
+ // > https://app.example.com/client-metadata.json could have a
736
+ // > redirect_uri of com.example.app:/callback.
781
737
  const protocol = `${reverseDomain(clientIdUrl.hostname)}:`
782
738
  if (url.protocol !== protocol) {
783
739
  throw new InvalidRedirectUriError(
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  OAuthClientIdDiscoverable,
3
+ isLocalHostname,
3
4
  parseOAuthDiscoverableClientId,
4
5
  } from '@atproto/oauth-types'
5
- import { isLocalHostname } from '@atproto-labs/fetch-node'
6
6
  import { InvalidClientIdError } from '../errors/invalid-client-id-error.js'
7
7
  import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'
8
8
 
@@ -106,8 +106,8 @@ export function buildMetadata(
106
106
 
107
107
  revocation_endpoint: new URL('/oauth/revoke', issuer).href,
108
108
 
109
- introspection_endpoint: new URL('/oauth/introspect', issuer).href,
110
-
109
+ // @TODO Should we implement these endpoints?
110
+ // introspection_endpoint: new URL('/oauth/introspect', issuer).href,
111
111
  // end_session_endpoint: new URL('/oauth/logout', issuer).href,
112
112
 
113
113
  // https://datatracker.ietf.org/doc/html/rfc9126#section-5
@@ -122,10 +122,10 @@ export function buildMetadata(
122
122
  authorization_details_types_supported:
123
123
  customMetadata?.authorization_details_types_supported,
124
124
 
125
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
125
+ // https://www.rfc-editor.org/rfc/rfc9728.html#section-4
126
126
  protected_resources: customMetadata?.protected_resources,
127
127
 
128
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
128
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
129
129
  client_id_metadata_document_supported: true,
130
130
  })
131
131
  }
@@ -115,6 +115,17 @@ export type OAuthHooks = {
115
115
  deviceMetadata: RequestMetadata
116
116
  }) => Awaitable<void>
117
117
 
118
+ /**
119
+ * This hook is called when a user requests a password reset, before the
120
+ * reset password request is triggered on the account store.
121
+ */
122
+ onResetPasswordRequested?: (data: {
123
+ input: ResetPasswordRequestInput
124
+ deviceId: DeviceId
125
+ deviceMetadata: RequestMetadata
126
+ account: Account
127
+ }) => Awaitable<void>
128
+
118
129
  /**
119
130
  * This hook is called when a user confirms a password reset, before the
120
131
  * password is actually reset on the account store.
@@ -125,6 +136,17 @@ export type OAuthHooks = {
125
136
  deviceMetadata: RequestMetadata
126
137
  }) => Awaitable<void>
127
138
 
139
+ /**
140
+ * This hook is called after a user confirms a password reset, and the
141
+ * password was successfully reset on the account store.
142
+ */
143
+ onResetPasswordConfirmed?: (data: {
144
+ input: ResetPasswordConfirmInput
145
+ deviceId: DeviceId
146
+ deviceMetadata: RequestMetadata
147
+ account: Account
148
+ }) => Awaitable<void>
149
+
128
150
  /**
129
151
  * This hook is called when a user successfully signs up.
130
152
  *