diff --git a/packages/pds/example.env b/packages/pds/example.env index fc3c3520eb0..4a833a09769 100644 --- a/packages/pds/example.env +++ b/packages/pds/example.env @@ -1,10 +1,10 @@ # See more env options in src/config/env.ts # Hostname - the public domain that you intend to deploy your service at PDS_HOSTNAME="example.com" +PDS_PORT="2583" # Database config - use one or the other -PDS_DB_SQLITE_LOCATION="db.test" -# PDS_DB_POSTGRES_URL="postgresql://pg:password@localhost:5433/postgres" +PDS_DATA_DIRECTORY="data" # Blobstore - filesystem location to store uploaded blobs PDS_BLOBSTORE_DISK_LOCATION="blobs" @@ -14,6 +14,7 @@ PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX="3ee68..." PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="e049f..." # Secrets - update to secure high-entropy strings +PDS_DPOP_SECRET="32-random-bytes-hex-encoded" PDS_JWT_SECRET="jwt-secret" PDS_ADMIN_PASSWORD="admin-pass" @@ -21,4 +22,21 @@ PDS_ADMIN_PASSWORD="admin-pass" PDS_DID_PLC_URL="https://plc.bsky-sandbox.dev" PDS_BSKY_APP_VIEW_ENDPOINT="https://api.bsky-sandbox.dev" PDS_BSKY_APP_VIEW_DID="did:web:api.bsky-sandbox.dev" -PDS_CRAWLERS="https://bgs.bsky-sandbox.dev" \ No newline at end of file +PDS_CRAWLERS="https://bgs.bsky-sandbox.dev" + +# OAuth Provider +PDS_OAUTH_PROVIDER_NAME="John's self hosted PDS" +PDS_OAUTH_PROVIDER_LOGO= +PDS_OAUTH_PROVIDER_PRIMARY_COLOR="#7507e3" +PDS_OAUTH_PROVIDER_ERROR_COLOR= +PDS_OAUTH_PROVIDER_HOME_LINK= +PDS_OAUTH_PROVIDER_TOS_LINK= +PDS_OAUTH_PROVIDER_POLICY_LINK= +PDS_OAUTH_PROVIDER_SUPPORT_LINK= + +# Debugging +NODE_TLS_REJECT_UNAUTHORIZED=1 +LOG_ENABLED=0 +LOG_LEVEL=info +PDS_INVITE_REQUIRED=1 +PDS_DISABLE_SSRF=0 diff --git a/packages/pds/package.json b/packages/pds/package.json index cbf28f5fadb..130ecceaf02 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -29,12 +29,14 @@ "migration:create": "ts-node ./bin/migration-create.ts" }, "dependencies": { + "@atproto-labs/fetch-node": "workspace:*", "@atproto/api": "workspace:^", "@atproto/aws": "workspace:^", "@atproto/common": "workspace:^", "@atproto/crypto": "workspace:^", "@atproto/identity": "workspace:^", "@atproto/lexicon": "workspace:^", + "@atproto/oauth-provider": "workspace:^", "@atproto/repo": "workspace:^", "@atproto/syntax": "workspace:^", "@atproto/xrpc": "workspace:^", diff --git a/packages/pds/src/account-manager/db/migrations/003-oauth.ts b/packages/pds/src/account-manager/db/migrations/003-oauth.ts new file mode 100644 index 00000000000..d0373fbba4a --- /dev/null +++ b/packages/pds/src/account-manager/db/migrations/003-oauth.ts @@ -0,0 +1,99 @@ +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('authorization_request') + .addColumn('id', 'varchar', (col) => col.primaryKey()) + .addColumn('did', 'varchar') + .addColumn('deviceId', 'varchar') + .addColumn('clientId', 'varchar', (col) => col.notNull()) + .addColumn('clientAuth', 'varchar', (col) => col.notNull()) + .addColumn('parameters', 'varchar', (col) => col.notNull()) + .addColumn('expiresAt', 'varchar', (col) => col.notNull()) + .addColumn('code', 'varchar') + .execute() + + await db.schema + .createIndex('authorization_request_code_idx') + .unique() + .on('authorization_request') + // https://github.com/kysely-org/kysely/issues/302 + .expression(sql`code DESC) WHERE (code IS NOT NULL`) + .execute() + + await db.schema + .createIndex('authorization_request_expires_at_idx') + .on('authorization_request') + .column('expiresAt') + .execute() + + await db.schema + .createTable('device') + .addColumn('id', 'varchar', (col) => col.primaryKey()) + .addColumn('sessionId', 'varchar', (col) => col.notNull()) + .addColumn('userAgent', 'varchar') + .addColumn('ipAddress', 'varchar', (col) => col.notNull()) + .addColumn('lastSeenAt', 'varchar', (col) => col.notNull()) + .addUniqueConstraint('device_session_id_idx', ['sessionId']) + .execute() + + await db.schema + .createTable('device_account') + .addColumn('did', 'varchar', (col) => col.notNull()) + .addColumn('deviceId', 'varchar', (col) => col.notNull()) + .addColumn('authenticatedAt', 'varchar', (col) => col.notNull()) + .addColumn('remember', 'boolean', (col) => col.notNull()) + .addColumn('authorizedClients', 'varchar', (col) => col.notNull()) + .addUniqueConstraint('device_account_did_device_id_idx', [ + 'deviceId', // first because this table will be joined from the "device" table + 'did', + ]) + .execute() + + await db.schema + .createTable('token') + .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement()) + .addColumn('did', 'varchar', (col) => col.notNull()) + .addColumn('tokenId', 'varchar', (col) => col.notNull()) + .addColumn('createdAt', 'varchar', (col) => col.notNull()) + .addColumn('updatedAt', 'varchar', (col) => col.notNull()) + .addColumn('expiresAt', 'varchar', (col) => col.notNull()) + .addColumn('clientId', 'varchar', (col) => col.notNull()) + .addColumn('clientAuth', 'varchar', (col) => col.notNull()) + .addColumn('deviceId', 'varchar') + .addColumn('parameters', 'varchar', (col) => col.notNull()) + .addColumn('details', 'varchar') + .addColumn('code', 'varchar') + .addColumn('currentRefreshToken', 'varchar') + .addUniqueConstraint('token_current_refresh_token_unique_idx', [ + 'currentRefreshToken', + ]) + .addUniqueConstraint('token_id_unique_idx', ['tokenId']) + .execute() + + await db.schema + .createIndex('token_code_idx') + .unique() + .on('token') + // https://github.com/kysely-org/kysely/issues/302 + .expression(sql`code DESC) WHERE (code IS NOT NULL`) + .execute() + + await db.schema + .createTable('used_refresh_token') + .addColumn('id', 'integer', (col) => col.notNull()) + .addColumn('usedRefreshToken', 'varchar', (col) => col.notNull()) + .addUniqueConstraint('used_refresh_token_used_refresh_token_idx', [ + 'usedRefreshToken', + 'id', + ]) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('used_refresh_token').execute() + await db.schema.dropTable('token').execute() + await db.schema.dropTable('device_account').execute() + await db.schema.dropTable('device').execute() + await db.schema.dropTable('authorization_request').execute() +} diff --git a/packages/pds/src/account-manager/db/migrations/index.ts b/packages/pds/src/account-manager/db/migrations/index.ts index 154dcd3ea9a..115bc3aeadf 100644 --- a/packages/pds/src/account-manager/db/migrations/index.ts +++ b/packages/pds/src/account-manager/db/migrations/index.ts @@ -1,7 +1,9 @@ import * as mig001 from './001-init' import * as mig002 from './002-account-deactivation' +import * as mig003 from './003-oauth' export default { '001': mig001, '002': mig002, + '003': mig003, } diff --git a/packages/pds/src/account-manager/db/schema/authorization-request.ts b/packages/pds/src/account-manager/db/schema/authorization-request.ts new file mode 100644 index 00000000000..4a81e46434f --- /dev/null +++ b/packages/pds/src/account-manager/db/schema/authorization-request.ts @@ -0,0 +1,26 @@ +import { + Code, + DeviceId, + OAuthClientId, + RequestId, +} from '@atproto/oauth-provider' +import { Selectable } from 'kysely' +import { DateISO, JsonObject } from '../../../db' + +export interface AuthorizationRequest { + id: RequestId + did: string | null + deviceId: DeviceId | null + + clientId: OAuthClientId + clientAuth: JsonObject + parameters: JsonObject + expiresAt: DateISO // TODO: Index this + code: Code | null +} + +export type AuthorizationRequestEntry = Selectable + +export const tableName = 'authorization_request' + +export type PartialDB = { [tableName]: AuthorizationRequest } diff --git a/packages/pds/src/account-manager/db/schema/device-account.ts b/packages/pds/src/account-manager/db/schema/device-account.ts new file mode 100644 index 00000000000..3cf6d139163 --- /dev/null +++ b/packages/pds/src/account-manager/db/schema/device-account.ts @@ -0,0 +1,15 @@ +import { DeviceId } from '@atproto/oauth-provider' +import { DateISO, JsonArray } from '../../../db' + +export interface DeviceAccount { + did: string + deviceId: DeviceId + + authenticatedAt: DateISO + authorizedClients: JsonArray + remember: 0 | 1 +} + +export const tableName = 'device_account' + +export type PartialDB = { [tableName]: DeviceAccount } diff --git a/packages/pds/src/account-manager/db/schema/device.ts b/packages/pds/src/account-manager/db/schema/device.ts new file mode 100644 index 00000000000..23b98acb0ba --- /dev/null +++ b/packages/pds/src/account-manager/db/schema/device.ts @@ -0,0 +1,18 @@ +import { DeviceId, SessionId } from '@atproto/oauth-provider' +import { Selectable } from 'kysely' +import { DateISO } from '../../../db' + +export interface Device { + id: DeviceId + sessionId: SessionId + + userAgent: string | null + ipAddress: string + lastSeenAt: DateISO +} + +export type DeviceEntry = Selectable + +export const tableName = 'device' + +export type PartialDB = { [tableName]: Device } diff --git a/packages/pds/src/account-manager/db/schema/index.ts b/packages/pds/src/account-manager/db/schema/index.ts index 6bcd95a9138..8280136c7f7 100644 --- a/packages/pds/src/account-manager/db/schema/index.ts +++ b/packages/pds/src/account-manager/db/schema/index.ts @@ -1,5 +1,10 @@ import * as actor from './actor' import * as account from './account' +import * as device from './device.js' +import * as deviceAccount from './device-account.js' +import * as oauthRequest from './authorization-request.js' +import * as token from './token.js' +import * as usedRefreshToken from './used-refresh-token.js' import * as repoRoot from './repo-root' import * as refreshToken from './refresh-token' import * as appPassword from './app-password' @@ -8,6 +13,11 @@ import * as emailToken from './email-token' export type DatabaseSchema = actor.PartialDB & account.PartialDB & + device.PartialDB & + deviceAccount.PartialDB & + oauthRequest.PartialDB & + token.PartialDB & + usedRefreshToken.PartialDB & refreshToken.PartialDB & appPassword.PartialDB & repoRoot.PartialDB & @@ -16,6 +26,11 @@ export type DatabaseSchema = actor.PartialDB & export type { Actor, ActorEntry } from './actor' export type { Account, AccountEntry } from './account' +export type { Device } from './device' +export type { DeviceAccount } from './device-account' +export type { AuthorizationRequest } from './authorization-request' +export type { Token } from './token' +export type { UsedRefreshToken } from './used-refresh-token' export type { RepoRoot } from './repo-root' export type { RefreshToken } from './refresh-token' export type { AppPassword } from './app-password' diff --git a/packages/pds/src/account-manager/db/schema/token.ts b/packages/pds/src/account-manager/db/schema/token.ts new file mode 100644 index 00000000000..dd40e0c4e69 --- /dev/null +++ b/packages/pds/src/account-manager/db/schema/token.ts @@ -0,0 +1,34 @@ +import { + Code, + DeviceId, + OAuthClientId, + RefreshToken, + Sub, + TokenId, +} from '@atproto/oauth-provider' +import { Generated, Selectable } from 'kysely' + +import { DateISO, JsonArray, JsonObject } from '../../../db/cast.js' + +export interface Token { + id: Generated + did: Sub + + tokenId: TokenId + createdAt: DateISO + updatedAt: DateISO + expiresAt: DateISO + clientId: OAuthClientId + clientAuth: JsonObject + deviceId: DeviceId | null + parameters: JsonObject + details: JsonArray | null + code: Code | null + currentRefreshToken: RefreshToken | null // TODO: Index this +} + +export type TokenEntry = Selectable + +export const tableName = 'token' + +export type PartialDB = { [tableName]: Token } diff --git a/packages/pds/src/account-manager/db/schema/used-refresh-token.ts b/packages/pds/src/account-manager/db/schema/used-refresh-token.ts new file mode 100644 index 00000000000..bd3cce74400 --- /dev/null +++ b/packages/pds/src/account-manager/db/schema/used-refresh-token.ts @@ -0,0 +1,13 @@ +import { RefreshToken } from '@atproto/oauth-provider' +import { Selectable } from 'kysely' + +export interface UsedRefreshToken { + id: number // TODO: Index this (foreign key to token) + usedRefreshToken: RefreshToken +} + +export type UsedRefreshTokenEntry = Selectable + +export const tableName = 'used_refresh_token' + +export type PartialDB = { [tableName]: UsedRefreshToken } diff --git a/packages/pds/src/account-manager/helpers/account.ts b/packages/pds/src/account-manager/helpers/account.ts index 344c06a3778..4917687184a 100644 --- a/packages/pds/src/account-manager/helpers/account.ts +++ b/packages/pds/src/account-manager/helpers/account.ts @@ -16,7 +16,7 @@ export type AvailabilityFlags = { includeDeactivated?: boolean } -const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => { +export const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => { const { includeTakenDown = false, includeDeactivated = false } = flags ?? {} const { ref } = db.db.dynamic return db.db diff --git a/packages/pds/src/account-manager/helpers/authorization-request.ts b/packages/pds/src/account-manager/helpers/authorization-request.ts new file mode 100644 index 00000000000..27852634068 --- /dev/null +++ b/packages/pds/src/account-manager/helpers/authorization-request.ts @@ -0,0 +1,107 @@ +import { + Code, + RequestData, + RequestId, + UpdateRequestData, +} from '@atproto/oauth-provider' +import { AccountDb, AuthorizationRequest } from '../db' +import { fromDateISO, fromJsonObject, toDateISO, toJsonObject } from '../../db' +import { Insertable, Selectable } from 'kysely' + +const rowToRequestData = ( + row: Selectable, +): RequestData => ({ + clientId: row.clientId, + clientAuth: fromJsonObject(row.clientAuth), + parameters: fromJsonObject(row.parameters), + expiresAt: fromDateISO(row.expiresAt), + deviceId: row.deviceId, + sub: row.did, + code: row.code, +}) + +const requestDataToRow = ( + id: RequestId, + data: RequestData, +): Insertable => ({ + id, + did: data.sub, + deviceId: data.deviceId, + + clientId: data.clientId, + clientAuth: toJsonObject(data.clientAuth), + parameters: toJsonObject(data.parameters), + expiresAt: toDateISO(data.expiresAt), + code: data.code, +}) + +export const create = async ( + db: AccountDb, + id: RequestId, + data: RequestData, +) => { + await db.db + .insertInto('authorization_request') + .values(requestDataToRow(id, data)) + .execute() +} + +export const deleteOldExpired = async (db: AccountDb, delay = 600e3) => { + // We allow some delay for the expiration time so that expired requests + // can still be returned to the OauthProvider library for error handling. + await db.db + .deleteFrom('authorization_request') + .where('expiresAt', '<', toDateISO(new Date(Date.now() - delay))) + .execute() +} + +export const get = async (db: AccountDb, id: RequestId) => { + const row = await db.db + .selectFrom('authorization_request') + .where('id', '=', id) + .selectAll() + .executeTakeFirst() + + if (!row) return null + return rowToRequestData(row) +} + +export const update = async ( + db: AccountDb, + id: RequestId, + data: UpdateRequestData, +) => { + const { code, sub, deviceId, expiresAt, ...rest } = data + + // Fool proof: in case the OauthProvider library is updated with new fields + for (const k in rest) throw new Error(`Unexpected update field "${k}"`) + + await db.db + .updateTable('authorization_request') + .if(code !== undefined, (qb) => qb.set({ code })) + .if(sub !== undefined, (qb) => qb.set({ did: sub })) + .if(deviceId !== undefined, (qb) => qb.set({ deviceId })) + .if(expiresAt != null, (qb) => qb.set({ expiresAt: toDateISO(expiresAt!) })) + .where('id', '=', id) + .execute() +} + +export const deleteById = async (db: AccountDb, id: RequestId) => { + await db.db.deleteFrom('authorization_request').where('id', '=', id).execute() +} + +export const findByCode = async (db: AccountDb, code: Code) => { + const row = await db.db + .selectFrom('authorization_request') + .where('code', '=', code) + .where('code', 'is not', null) // use "authorization_request_code_idx" + .selectAll() + .executeTakeFirst() + + if (!row) return null + + return { + id: row.id, + data: rowToRequestData(row), + } +} diff --git a/packages/pds/src/account-manager/helpers/device-account.ts b/packages/pds/src/account-manager/helpers/device-account.ts new file mode 100644 index 00000000000..b696c7e34ed --- /dev/null +++ b/packages/pds/src/account-manager/helpers/device-account.ts @@ -0,0 +1,180 @@ +import { + Account, + DeviceAccountInfo, + DeviceId, + OAuthClientId, +} from '@atproto/oauth-provider' +import { Insertable, Selectable } from 'kysely' + +import { fromDateISO, fromJsonArray, toDateISO, toJsonArray } from '../../db' +import { AccountDb } from '../db' +import { DeviceAccount } from '../db/schema/device-account' +import { ActorAccount, selectAccountQB } from './account' + +export type SelectableDeviceAccount = Pick< + Selectable, + 'authenticatedAt' | 'authorizedClients' | 'remember' +> + +const selectAccountInfoQB = (db: AccountDb, deviceId: DeviceId) => + selectAccountQB(db) + .innerJoin('device_account', 'device_account.did', 'actor.did') + .innerJoin('device', 'device.id', 'device_account.deviceId') + .where('device.id', '=', deviceId) + .select([ + 'device_account.authenticatedAt', + 'device_account.remember', + 'device_account.authorizedClients', + ]) + +export type InsertableField = { + authenticatedAt: Date + authorizedClients: OAuthClientId[] + remember: boolean +} + +function toInsertable>( + values: V, +): Pick, keyof V & keyof Insertable> +function toInsertable( + values: Partial, +): Partial> { + const row: Partial> = {} + if (values.authenticatedAt) { + row.authenticatedAt = toDateISO(values.authenticatedAt) + } + if (values.remember !== undefined) { + row.remember = values.remember === true ? 1 : 0 + } + if (values.authorizedClients) { + row.authorizedClients = toJsonArray(values.authorizedClients) + } + return row +} + +export function toDeviceAccountInfo( + row: SelectableDeviceAccount, +): DeviceAccountInfo { + return { + remembered: row.remember === 1, + authenticatedAt: fromDateISO(row.authenticatedAt), + authorizedClients: fromJsonArray(row.authorizedClients), + } +} + +export function toAccount( + row: Selectable, + audience: string, +): Account { + return { + sub: row.did, + aud: audience, + email: row.email || undefined, + email_verified: row.email ? row.emailConfirmedAt != null : undefined, + preferred_username: row.handle || undefined, + } +} + +export const getAuthorizedClients = async ( + db: AccountDb, + deviceId: DeviceId, + did: string, +) => { + const row = await db.db + .selectFrom('device_account') + .where('did', '=', did) + .where('deviceId', '=', deviceId) + .select('authorizedClients') + .executeTakeFirstOrThrow() + + return fromJsonArray(row.authorizedClients) +} + +export const update = async ( + db: AccountDb, + deviceId: DeviceId, + did: string, + entry: { + authenticatedAt?: Date + authorizedClients?: OAuthClientId[] + remember?: boolean + }, +): Promise => { + await db.db + .updateTable('device_account') + .set(toInsertable(entry)) + .where('did', '=', did) + .where('deviceId', '=', deviceId) + .execute() +} + +export const createOrUpdate = async ( + db: AccountDb, + deviceId: DeviceId, + did: string, + remember: boolean, +) => { + const { authorizedClients, ...values } = toInsertable({ + remember, + authenticatedAt: new Date(), + authorizedClients: [], + }) + + await db.db + .insertInto('device_account') + .values({ did, deviceId, authorizedClients, ...values }) + .onConflict((oc) => oc.columns(['deviceId', 'did']).doUpdateSet(values)) + .executeTakeFirstOrThrow() +} + +export const get = async ( + db: AccountDb, + deviceId: DeviceId, + did: string, + audience: string, +) => { + const row = await selectAccountInfoQB(db, deviceId) + .where('actor.did', '=', did) + .executeTakeFirst() + + if (!row) return null + + return { + account: toAccount(row, audience), + info: toDeviceAccountInfo(row), + } +} + +export const listRemembered = async ( + db: AccountDb, + deviceId: DeviceId, + audience: string, +) => { + const rows = await selectAccountInfoQB(db, deviceId) + .where('device_account.remember', '=', 1) + .execute() + + return rows.map((row) => ({ + account: toAccount(row, audience), + info: toDeviceAccountInfo(row), + })) +} + +export const remove = async ( + db: AccountDb, + deviceId: DeviceId, + did: string, +) => { + await db.db + .deleteFrom('device_account') + .where('deviceId', '=', deviceId) + .where('did', '=', did) + .execute() +} + +export const removeByDevice = async (db: AccountDb, deviceId: DeviceId) => { + await db.db + .deleteFrom('device_account') + .where('deviceId', '=', deviceId) + .execute() +} diff --git a/packages/pds/src/account-manager/helpers/device.ts b/packages/pds/src/account-manager/helpers/device.ts new file mode 100644 index 00000000000..7c540dd6da4 --- /dev/null +++ b/packages/pds/src/account-manager/helpers/device.ts @@ -0,0 +1,76 @@ +import { DeviceId, DeviceData } from '@atproto/oauth-provider' +import { AccountDb, Device } from '../db' +import { fromDateISO, toDateISO } from '../../db' +import { Selectable } from 'kysely' + +const rowToDeviceData = (row: Selectable): DeviceData => ({ + sessionId: row.sessionId, + userAgent: row.userAgent, + ipAddress: row.ipAddress, + lastSeenAt: fromDateISO(row.lastSeenAt), +}) + +/** + * Future-proofs the session data by ensuring that only the expected fields are + * present. If the @atproto/oauth-provider package adds new fields to the + * DeviceData type, this function will throw an error. + */ +const futureProof = >(data: T): T => { + const { sessionId, userAgent, ipAddress, lastSeenAt, ...rest } = data + if (Object.keys(rest).length > 0) throw new Error('Unexpected fields') + return { sessionId, userAgent, ipAddress, lastSeenAt } as T +} + +export const create = async ( + db: AccountDb, + deviceId: DeviceId, + data: DeviceData, +) => { + const { sessionId, userAgent, ipAddress, lastSeenAt } = futureProof(data) + + await db.db + .insertInto('device') + .values({ + id: deviceId, + sessionId, + userAgent, + ipAddress, + lastSeenAt: toDateISO(lastSeenAt), + }) + .execute() +} + +export const getById = async (db: AccountDb, deviceId: DeviceId) => { + const row = await db.db + .selectFrom('device') + .where('id', '=', deviceId) + .selectAll() + .executeTakeFirst() + + if (row == null) return null + + return rowToDeviceData(row) +} + +export const update = async ( + db: AccountDb, + deviceId: DeviceId, + data: Partial, +) => { + const { sessionId, userAgent, ipAddress, lastSeenAt } = futureProof(data) + + await db.db + .updateTable('device') + .if(sessionId != null, (qb) => qb.set({ sessionId })) + .if(userAgent != null, (qb) => qb.set({ userAgent })) + .if(ipAddress != null, (qb) => qb.set({ ipAddress })) + .if(lastSeenAt != null, (qb) => + qb.set({ lastSeenAt: toDateISO(lastSeenAt!) }), + ) + .where('id', '=', deviceId) + .execute() +} + +export const remove = async (db: AccountDb, deviceId: DeviceId) => { + await db.db.deleteFrom('device').where('id', '=', deviceId).execute() +} diff --git a/packages/pds/src/account-manager/helpers/token.ts b/packages/pds/src/account-manager/helpers/token.ts new file mode 100644 index 00000000000..9929e983cc3 --- /dev/null +++ b/packages/pds/src/account-manager/helpers/token.ts @@ -0,0 +1,228 @@ +import { + Code, + NewTokenData, + OAuthAuthorizationDetails, + RefreshToken, + TokenData, + TokenId, + TokenInfo, +} from '@atproto/oauth-provider' +import { Insertable, Selectable } from 'kysely' +import { + fromDateISO, + fromJsonArray, + fromJsonObject, + toDateISO, + toJsonArray, + toJsonObject, +} from '../../db' +import { AccountDb, Token } from '../db' +import { ActorAccount, selectAccountQB } from './account' +import { + SelectableDeviceAccount, + toAccount, + toDeviceAccountInfo, +} from './device-account' + +type LeftJoined = { [K in keyof T]: null | T[K] } + +export type ActorAccountToken = Selectable & + Selectable> & + LeftJoined + +export const toInsertable = ( + tokenId: TokenId, + data: TokenData, + refreshToken?: RefreshToken, +): Insertable => ({ + tokenId, + createdAt: toDateISO(data.createdAt), + expiresAt: toDateISO(data.expiresAt), + updatedAt: toDateISO(data.updatedAt), + clientId: data.clientId, + clientAuth: toJsonObject(data.clientAuth), + deviceId: data.deviceId, + did: data.sub, + parameters: toJsonObject(data.parameters), + details: data.details ? toJsonArray(data.details) : null, + code: data.code, + currentRefreshToken: refreshToken || null, +}) + +export const toTokenData = ( + row: Pick, +): TokenData => ({ + createdAt: fromDateISO(row.createdAt), + expiresAt: fromDateISO(row.expiresAt), + updatedAt: fromDateISO(row.updatedAt), + clientId: row.clientId, + clientAuth: fromJsonObject(row.clientAuth), + deviceId: row.deviceId, + sub: row.did, + parameters: fromJsonObject(row.parameters), + details: row.details + ? fromJsonArray(row.details) + : null, + code: row.code, +}) + +export const toTokenInfo = ( + row: ActorAccountToken, + audience: string, +): TokenInfo => ({ + id: row.tokenId, + data: toTokenData(row), + account: toAccount(row, audience), + info: + row.authenticatedAt != null && + row.authorizedClients != null && + row.remember != null + ? toDeviceAccountInfo(row as SelectableDeviceAccount) + : undefined, + currentRefreshToken: row.currentRefreshToken, +}) + +const selectTokenInfoQB = (db: AccountDb) => + selectAccountQB(db) + .innerJoin('token', 'token.did', 'actor.did') + .leftJoin('device_account', (join) => + join + .on('device_account.did', '=', 'token.did') + // @ts-expect-error "deviceId" is nullable in token + .on('device_account.deviceId', '=', 'token.deviceId'), + ) + .select([ + 'token.tokenId', + 'token.createdAt', + 'token.updatedAt', + 'token.expiresAt', + 'token.clientId', + 'token.clientAuth', + 'token.deviceId', + 'token.did', + 'token.parameters', + 'token.details', + 'token.code', + 'token.currentRefreshToken', + 'device_account.authenticatedAt', + 'device_account.authorizedClients', + 'device_account.remember', + ]) + +export const create = async ( + db: AccountDb, + tokenId: TokenId, + data: TokenData, + refreshToken?: RefreshToken, +) => { + await db.db + .insertInto('token') + .values(toInsertable(tokenId, data, refreshToken)) + .execute() +} + +export const getForRefresh = async (db: AccountDb, id: TokenId) => { + return db.db + .selectFrom('token') + .where('tokenId', '=', id) + .where('currentRefreshToken', 'is not', null) + .select(['id', 'currentRefreshToken']) + .executeTakeFirstOrThrow() +} + +export const findBy = async ( + db: AccountDb, + search: { + tokenId?: TokenId + id?: number + currentRefreshToken?: RefreshToken + }, + audience: string, +): Promise => { + if ( + search.id === undefined && + search.tokenId === undefined && + search.currentRefreshToken === undefined + ) { + // Prevent accidental scan + throw new Error('At least one search parameter is required') + } + + const row = await selectTokenInfoQB(db) + .if(search.id !== undefined, (qb) => + // primary key + qb.where('token.id', '=', search.id!), + ) + .if(search.tokenId !== undefined, (qb) => + // uses "token_token_id_idx" + qb.where('token.tokenId', '=', search.tokenId!), + ) + .if(search.currentRefreshToken !== undefined, (qb) => + // uses "token_refresh_token_unique_idx" + qb.where('token.currentRefreshToken', '=', search.currentRefreshToken!), + ) + .executeTakeFirst() + + if (!row) return null + + return toTokenInfo(row, audience) +} + +export const rotate = async ( + db: AccountDb, + id: number, + newTokenId: TokenId, + newRefreshToken: RefreshToken, + newData: NewTokenData, +) => { + const { expiresAt, updatedAt, clientAuth, ...rest } = newData + + // Future proofing + if (Object.keys(rest).length > 0) throw new Error('Unexpected fields') + + await db.db + .updateTable('token') + .set({ + tokenId: newTokenId, + currentRefreshToken: newRefreshToken, + + expiresAt: toDateISO(expiresAt), + updatedAt: toDateISO(updatedAt), + clientAuth: toJsonObject(clientAuth), + }) + .where('id', '=', id) + .execute() +} + +export const remove = async ( + db: AccountDb, + tokenId: TokenId, +): Promise => { + const row = await db.db + .deleteFrom('token') + .where('tokenId', '=', tokenId) + .returning('id') + .executeTakeFirst() + + if (!row) return + + // TODO: can use use foreign key constraint to delete this row ? + await db.db + .deleteFrom('used_refresh_token') + .where('id', '=', row.id) + .execute() +} + +export const findByCode = async ( + db: AccountDb, + code: Code, + audience: string, +): Promise => { + const row = await selectTokenInfoQB(db) + .where('code', '=', code) + .where('code', 'is not', null) // uses "token_code_idx" + .executeTakeFirst() + + if (!row) return null + return toTokenInfo(row, audience) +} diff --git a/packages/pds/src/account-manager/helpers/used-refresh-token.ts b/packages/pds/src/account-manager/helpers/used-refresh-token.ts new file mode 100644 index 00000000000..2eb23465d5a --- /dev/null +++ b/packages/pds/src/account-manager/helpers/used-refresh-token.ts @@ -0,0 +1,24 @@ +import { RefreshToken } from '@atproto/oauth-provider' +import { AccountDb } from '../db' + +export const insert = async ( + db: AccountDb, + id: number, + usedRefreshToken: RefreshToken, +) => { + await db.executeWithRetry( + db.db + .insertInto('used_refresh_token') + .values({ id, usedRefreshToken }) + .onConflict((oc) => oc.doNothing()), + ) +} + +export const findByToken = (db: AccountDb, usedRefreshToken: RefreshToken) => { + return db.db + .selectFrom('used_refresh_token') + .where('usedRefreshToken', '=', usedRefreshToken) + .select('id') + .executeTakeFirst() + .then((row) => row?.id ?? null) +} diff --git a/packages/pds/src/account-manager/index.ts b/packages/pds/src/account-manager/index.ts index 89fb4ac00b1..09d9152d96b 100644 --- a/packages/pds/src/account-manager/index.ts +++ b/packages/pds/src/account-manager/index.ts @@ -1,6 +1,29 @@ import { KeyObject } from 'node:crypto' -import { HOUR } from '@atproto/common' +import { HOUR, wait } from '@atproto/common' +import { + AccountInfo, + AccountStore, + Code, + DeviceData, + DeviceId, + FoundRequestResult, + LoginCredentials, + NewTokenData, + RefreshToken, + RequestData, + RequestId, + RequestStore, + DeviceStore, + TokenData, + TokenId, + TokenInfo, + TokenStore, + UpdateRequestData, +} from '@atproto/oauth-provider' +import { AuthRequiredError } from '@atproto/xrpc-server' import { CID } from 'multiformats/cid' + +import { softDeleted } from '../db' import { AccountDb, EmailTokenPurpose, getDb, getMigrator } from './db' import * as scrypt from './helpers/scrypt' import * as account from './helpers/account' @@ -10,10 +33,17 @@ import * as auth from './helpers/auth' import * as invite from './helpers/invite' import * as password from './helpers/password' import * as emailToken from './helpers/email-token' +import * as authorizationRequest from './helpers/authorization-request.js' +import * as device from './helpers/device.js' +import * as deviceAccount from './helpers/device-account.js' +import * as token from './helpers/token.js' +import * as usedRefreshToken from './helpers/used-refresh-token.js' import { AuthScope } from '../auth-verifier' import { StatusAttr } from '../lexicon/types/com/atproto/admin/defs' -export class AccountManager { +export class AccountManager + implements AccountStore, RequestStore, DeviceStore, TokenStore +{ db: AccountDb constructor( @@ -235,6 +265,59 @@ export class AccountManager { return auth.revokeRefreshToken(this.db, id) } + // Login + // ---------- + + async login( + { identifier, password }: { identifier: string; password: string }, + allowAppPassword = false, + ): Promise<{ + user: ActorAccount + appPasswordName: string | null + }> { + const start = Date.now() + try { + const identifierNormalized = identifier.toLowerCase() + const user = identifier.includes('@') + ? await this.getAccountByEmail(identifierNormalized, { + includeDeactivated: true, + includeTakenDown: true, + }) + : await this.getAccount(identifierNormalized, { + includeDeactivated: true, + includeTakenDown: true, + }) + + if (!user) throw new AuthRequiredError('Invalid identifier or password') + + let appPasswordName: string | null = null + const validAccountPass = await this.verifyAccountPassword( + user.did, + password, + ) + if (!validAccountPass) { + if (allowAppPassword) { + appPasswordName = await this.verifyAppPassword(user.did, password) + } + if (appPasswordName === null) { + throw new AuthRequiredError('Invalid identifier or password') + } + } + + if (softDeleted(user)) { + throw new AuthRequiredError( + 'Account has been taken down', + 'AccountTakedown', + ) + } + + return { user, appPasswordName } + } finally { + // Mitigate timing attacks + await wait(350 - (Date.now() - start)) + } + } + // Passwords // ---------- @@ -384,4 +467,166 @@ export class AccountManager { ]), ) } + + // AccountStore + + async authenticateAccount( + { username: identifier, password, remember = false }: LoginCredentials, + deviceId: DeviceId, + ): Promise { + try { + const { user } = await this.login({ identifier, password }, false) + + await deviceAccount.createOrUpdate(this.db, deviceId, user.did, remember) + + return deviceAccount.get(this.db, deviceId, user.did, this.serviceDid) + } catch (err) { + if (err instanceof AuthRequiredError) return null + throw err + } + } + + async addAuthorizedClient( + deviceId: DeviceId, + sub: string, + clientId: string, + ): Promise { + await this.db.transaction(async (dbTxn) => { + const authorizedClients = await deviceAccount.getAuthorizedClients( + dbTxn, + deviceId, + sub, + ) + + if (authorizedClients.includes(clientId)) return + + await deviceAccount.update(dbTxn, deviceId, sub, { + authorizedClients: [...authorizedClients, clientId], + }) + }) + } + + async getDeviceAccount( + deviceId: DeviceId, + sub: string, + ): Promise { + return deviceAccount.get(this.db, deviceId, sub, this.serviceDid) + } + + async listDeviceAccounts(deviceId: DeviceId): Promise { + return deviceAccount.listRemembered(this.db, deviceId, this.serviceDid) + } + + async removeDeviceAccount(deviceId: DeviceId, sub: string): Promise { + return deviceAccount.remove(this.db, deviceId, sub) + } + + // RequestStore + + async createRequest(id: RequestId, data: RequestData): Promise { + await authorizationRequest.create(this.db, id, data) + } + + async readRequest(id: RequestId): Promise { + try { + return authorizationRequest.get(this.db, id) + } finally { + // Take the opportunity to clean up expired requests. Do this after we got + // the current (potentially expired) request data to allow the provider to + // handle expired requests. + + // TODO: Do this less often? + await authorizationRequest.deleteOldExpired(this.db) + } + } + + async updateRequest(id: RequestId, data: UpdateRequestData): Promise { + await authorizationRequest.update(this.db, id, data) + } + + async deleteRequest(id: RequestId): Promise { + await authorizationRequest.deleteById(this.db, id) + } + + async findRequestByCode(code: Code): Promise { + return authorizationRequest.findByCode(this.db, code) + } + + // DeviceStore + + async createDevice(deviceId: DeviceId, data: DeviceData): Promise { + await device.create(this.db, deviceId, data) + } + + async readDevice(deviceId: DeviceId): Promise { + return device.getById(this.db, deviceId) + } + + async updateDevice( + deviceId: DeviceId, + data: Partial, + ): Promise { + await device.update(this.db, deviceId, data) + } + + async deleteDevice(deviceId: DeviceId): Promise { + await device.remove(this.db, deviceId) + + // TODO: can use use foreign key constraint to delete this row ? + await deviceAccount.removeByDevice(this.db, deviceId) + } + + // TokenStore + + async createToken( + id: TokenId, + data: TokenData, + refreshToken?: RefreshToken, + ): Promise { + await token.create(this.db, id, data, refreshToken) + } + + async readToken(tokenId: TokenId): Promise { + return token.findBy(this.db, { tokenId }, this.serviceDid) + } + + async deleteToken(tokenId: TokenId): Promise { + await token.remove(this.db, tokenId) + } + + async rotateToken( + tokenId: TokenId, + newTokenId: TokenId, + newRefreshToken: RefreshToken, + newData: NewTokenData, + ): Promise { + // No transaction because we want to make sure that the token is added + // to the used refresh tokens even if the rotate() fails. + + const { id, currentRefreshToken } = await token.getForRefresh( + this.db, + tokenId, + ) + + if (currentRefreshToken) { + await usedRefreshToken.insert(this.db, id, currentRefreshToken) + } + + await token.rotate(this.db, id, newTokenId, newRefreshToken, newData) + } + + async findTokenByRefreshToken( + refreshToken: RefreshToken, + ): Promise { + const id = await usedRefreshToken.findByToken(this.db, refreshToken) + return token.findBy( + this.db, + id ? { id } : { currentRefreshToken: refreshToken }, + this.serviceDid, + ) + } + + async findTokenByCode(code: Code): Promise { + return token.findByCode(this.db, code, this.serviceDid) + } } diff --git a/packages/pds/src/api/com/atproto/server/createSession.ts b/packages/pds/src/api/com/atproto/server/createSession.ts index c315f726f3a..4fb813f681b 100644 --- a/packages/pds/src/api/com/atproto/server/createSession.ts +++ b/packages/pds/src/api/com/atproto/server/createSession.ts @@ -1,11 +1,10 @@ import { DAY, MINUTE } from '@atproto/common' import { INVALID_HANDLE } from '@atproto/syntax' -import { AuthRequiredError } from '@atproto/xrpc-server' + import AppContext from '../../../../context' -import { softDeleted } from '../../../../db/util' import { Server } from '../../../../lexicon' -import { didDocForSession } from './util' import { authPassthru, resultPassthru } from '../../../proxy' +import { didDocForSession } from './util' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.createSession({ @@ -31,44 +30,10 @@ export default function (server: Server, ctx: AppContext) { ) } - const { password } = input.body - const identifier = input.body.identifier.toLowerCase() - - const user = identifier.includes('@') - ? await ctx.accountManager.getAccountByEmail(identifier, { - includeDeactivated: true, - includeTakenDown: true, - }) - : await ctx.accountManager.getAccount(identifier, { - includeDeactivated: true, - includeTakenDown: true, - }) - - if (!user) { - throw new AuthRequiredError('Invalid identifier or password') - } - - let appPasswordName: string | null = null - const validAccountPass = await ctx.accountManager.verifyAccountPassword( - user.did, - password, + const { user, appPasswordName } = await ctx.accountManager.login( + input.body, + true, ) - if (!validAccountPass) { - appPasswordName = await ctx.accountManager.verifyAppPassword( - user.did, - password, - ) - if (appPasswordName === null) { - throw new AuthRequiredError('Invalid identifier or password') - } - } - - if (softDeleted(user)) { - throw new AuthRequiredError( - 'Account has been taken down', - 'AccountTakedown', - ) - } const [{ accessJwt, refreshJwt }, didDoc] = await Promise.all([ ctx.accountManager.createSession(user.did, appPasswordName), diff --git a/packages/pds/src/api/com/atproto/server/deleteSession.ts b/packages/pds/src/api/com/atproto/server/deleteSession.ts index 22a2055048f..bd3778a0b12 100644 --- a/packages/pds/src/api/com/atproto/server/deleteSession.ts +++ b/packages/pds/src/api/com/atproto/server/deleteSession.ts @@ -1,28 +1,22 @@ -import { AuthScope } from '../../../../auth-verifier' import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { - server.com.atproto.server.deleteSession(async ({ req }) => { - if (ctx.entrywayAgent) { - await ctx.entrywayAgent.com.atproto.server.deleteSession( + const { entrywayAgent } = ctx + if (entrywayAgent) { + server.com.atproto.server.deleteSession(async (reqCtx) => { + await entrywayAgent.com.atproto.server.deleteSession( undefined, - authPassthru(req, true), + authPassthru(reqCtx.req, true), ) - return - } - - const result = await ctx.authVerifier.validateBearerToken( - req, - [AuthScope.Refresh], - { clockTolerance: Infinity }, // ignore expiration - ) - const id = result.payload.jti - if (!id) { - throw new Error('Unexpected missing refresh token id') - } - - await ctx.accountManager.revokeRefreshToken(id) - }) + }) + } else { + server.com.atproto.server.deleteSession({ + auth: ctx.authVerifier.refreshExpired, + handler: async ({ auth }) => { + await ctx.accountManager.revokeRefreshToken(auth.credentials.tokenId) + }, + }) + } } diff --git a/packages/pds/src/api/proxy.ts b/packages/pds/src/api/proxy.ts index 554898dbbaf..ad16a337a14 100644 --- a/packages/pds/src/api/proxy.ts +++ b/packages/pds/src/api/proxy.ts @@ -1,4 +1,5 @@ import { Headers } from '@atproto/xrpc' +import { InvalidRequestError } from '@atproto/xrpc-server' import { IncomingMessage } from 'node:http' export const resultPassthru = (result: { headers: Headers; data: T }) => { @@ -24,9 +25,24 @@ export function authPassthru( | undefined export function authPassthru(req: IncomingMessage, withEncoding?: boolean) { - if (req.headers.authorization) { + const { authorization } = req.headers + + if (authorization) { + // DPoP requests are bound to the endpoint being called. Allowing them to be + // proxied would require that the receiving end allows DPoP proof not + // created for him. Since proxying is mainly there to support legacy + // clients, and DPoP is a new feature, we don't support DPoP requests + // through the proxy. + + // This is fine since app views are usually called using the requester's + // credentials when "auth.credentials.type === 'access'", which is the only + // case were DPoP is used. + if (authorization.startsWith('DPoP ') || req.headers['dpop']) { + throw new InvalidRequestError('DPoP requests cannot be proxied') + } + return { - headers: { authorization: req.headers.authorization }, + headers: { authorization }, encoding: withEncoding ? 'application/json' : undefined, } } diff --git a/packages/pds/src/auth-routes.ts b/packages/pds/src/auth-routes.ts new file mode 100644 index 00000000000..7b56b8e649e --- /dev/null +++ b/packages/pds/src/auth-routes.ts @@ -0,0 +1,11 @@ +import { type RequestHandler } from 'express' +import AppContext from './context' + +export const createRouter = (ctx: AppContext): RequestHandler => { + return ( + ctx.authProvider?.createRouter() ?? + ((req, res, next) => { + next() + }) + ) +} diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index 7994cc7ddaf..17615ac82c2 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -1,8 +1,15 @@ import { KeyObject, createPublicKey, createSecretKey } from 'node:crypto' + +import { + OAuthError, + OAuthVerifier, + WWWAuthenticateError, +} from '@atproto/oauth-provider' import { AuthRequiredError, ForbiddenError, InvalidRequestError, + XRPCError, verifyJwt as verifyServiceJwt, } from '@atproto/xrpc-server' import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity' @@ -89,7 +96,12 @@ type ValidatedBearer = { audience: string | undefined } +type ValidatedRefreshBearer = ValidatedBearer & { + tokenId: string +} + export type AuthVerifierOpts = { + publicUrl: string jwtKey: KeyObject adminPass: string dids: { @@ -100,6 +112,7 @@ export type AuthVerifierOpts = { } export class AuthVerifier { + private _publicUrl: string private _jwtKey: KeyObject private _adminPass: string public dids: AuthVerifierOpts['dids'] @@ -107,8 +120,10 @@ export class AuthVerifier { constructor( public accountManager: AccountManager, public idResolver: IdResolver, + public oauthVerifier: OAuthVerifier, opts: AuthVerifierOpts, ) { + this._publicUrl = opts.publicUrl this._jwtKey = opts.jwtKey this._adminPass = opts.adminPass this.dids = opts.dids @@ -116,15 +131,14 @@ export class AuthVerifier { // verifiers (arrow fns to preserve scope) - access = (ctx: ReqCtx): Promise => { - return this.validateAccessToken(ctx.req, [ - AuthScope.Access, - AuthScope.AppPass, - ]) + access = async (ctx: ReqCtx): Promise => { + this.setAuthHeaders(ctx) + return this.validateAccessToken(ctx, [AuthScope.Access, AuthScope.AppPass]) } accessCheckTakedown = async (ctx: ReqCtx): Promise => { - const result = await this.validateAccessToken(ctx.req, [ + this.setAuthHeaders(ctx) + const result = await this.validateAccessToken(ctx, [ AuthScope.Access, AuthScope.AppPass, ]) @@ -144,12 +158,14 @@ export class AuthVerifier { return result } - accessNotAppPassword = (ctx: ReqCtx): Promise => { - return this.validateAccessToken(ctx.req, [AuthScope.Access]) + accessNotAppPassword = async (ctx: ReqCtx): Promise => { + this.setAuthHeaders(ctx) + return this.validateAccessToken(ctx, [AuthScope.Access]) } - accessDeactived = (ctx: ReqCtx): Promise => { - return this.validateAccessToken(ctx.req, [ + accessDeactived = async (ctx: ReqCtx): Promise => { + this.setAuthHeaders(ctx) + return this.validateAccessToken(ctx, [ AuthScope.Access, AuthScope.AppPass, AuthScope.Deactivated, @@ -157,30 +173,41 @@ export class AuthVerifier { } refresh = async (ctx: ReqCtx): Promise => { - const { did, scope, token, audience, payload } = - await this.validateBearerToken(ctx.req, [AuthScope.Refresh], { - // when using entryway, proxying refresh credentials - audience: this.dids.entryway ? this.dids.entryway : this.dids.pds, - }) - if (!payload.jti) { - throw new AuthRequiredError( - 'Unexpected missing refresh token id', - 'MissingTokenId', - ) + this.setAuthHeaders(ctx) + const { did, scope, token, tokenId, audience } = + await this.validateRefreshToken(ctx.req) + + return { + credentials: { + type: 'refresh', + did, + scope, + audience, + tokenId, + }, + artifacts: token, } + } + + refreshExpired = async (ctx: ReqCtx): Promise => { + this.setAuthHeaders(ctx) + const { did, scope, token, tokenId, audience } = + await this.validateRefreshToken(ctx.req, { clockTolerance: Infinity }) + return { credentials: { type: 'refresh', did, scope, audience, - tokenId: payload.jti, + tokenId, }, artifacts: token, } } - adminToken = (ctx: ReqCtx): AdminTokenOutput => { + adminToken = async (ctx: ReqCtx): Promise => { + this.setAuthHeaders(ctx) const parsed = parseBasicAuth(ctx.req.headers.authorization || '') if (!parsed) { throw new AuthRequiredError() @@ -195,7 +222,8 @@ export class AuthVerifier { optionalAccessOrAdminToken = async ( ctx: ReqCtx, ): Promise => { - if (isBearerToken(ctx.req)) { + this.setAuthHeaders(ctx) + if (isAccessToken(ctx.req)) { return await this.access(ctx) } else if (isBasicToken(ctx.req)) { return await this.adminToken(ctx) @@ -205,6 +233,7 @@ export class AuthVerifier { } userDidAuth = async (reqCtx: ReqCtx): Promise => { + this.setAuthHeaders(reqCtx) const payload = await this.verifyServiceJwt(reqCtx, { aud: this.dids.entryway ?? this.dids.pds, iss: null, @@ -221,6 +250,7 @@ export class AuthVerifier { userDidAuthOptional = async ( reqCtx: ReqCtx, ): Promise => { + this.setAuthHeaders(reqCtx) if (isBearerToken(reqCtx.req)) { return await this.userDidAuth(reqCtx) } else { @@ -229,6 +259,7 @@ export class AuthVerifier { } modService = async (reqCtx: ReqCtx): Promise => { + this.setAuthHeaders(reqCtx) if (!this.dids.modService) { throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') } @@ -257,6 +288,7 @@ export class AuthVerifier { moderator = async ( reqCtx: ReqCtx, ): Promise => { + this.setAuthHeaders(reqCtx) if (isBearerToken(reqCtx.req)) { return this.modService(reqCtx) } else { @@ -264,7 +296,26 @@ export class AuthVerifier { } } - async validateBearerToken( + protected async validateRefreshToken( + req: express.Request, + verifyOptions?: Omit, + ): Promise { + const result = await this.validateBearerToken(req, [AuthScope.Refresh], { + ...verifyOptions, + // when using entryway, proxying refresh credentials + audience: this.dids.entryway ? this.dids.entryway : this.dids.pds, + }) + const tokenId = result.payload.jti + if (!tokenId) { + throw new AuthRequiredError( + 'Unexpected missing refresh token id', + 'MissingTokenId', + ) + } + return { ...result, tokenId } + } + + protected async validateBearerToken( req: express.Request, scopes: AuthScope[], verifyOptions?: jose.JWTVerifyOptions, @@ -273,7 +324,17 @@ export class AuthVerifier { if (!token) { throw new AuthRequiredError(undefined, 'AuthMissing') } - const payload = await verifyJwt({ key: this._jwtKey, token, verifyOptions }) + + const { payload, protectedHeader } = await this.jwtVerify( + token, + verifyOptions, + ) + + if (protectedHeader.typ) { + // Only OAuth Provider sets this claim + throw new InvalidRequestError('Malformed token', 'InvalidToken') + } + const { sub, aud, scope } = payload if (typeof sub !== 'string' || !sub.startsWith('did:')) { throw new InvalidRequestError('Malformed token', 'InvalidToken') @@ -284,6 +345,10 @@ export class AuthVerifier { ) { throw new InvalidRequestError('Malformed token', 'InvalidToken') } + if ((payload.cnf as any)?.jkt) { + // DPoP bound tokens must not be usable as regular Bearer tokens + throw new InvalidRequestError('Malformed token', 'InvalidToken') + } if (!isAuthScope(scope) || (scopes.length > 0 && !scopes.includes(scope))) { throw new InvalidRequestError('Bad token scope', 'InvalidToken') } @@ -297,7 +362,76 @@ export class AuthVerifier { } async validateAccessToken( - req: express.Request, + ctx: ReqCtx, + scopes: AuthScope[], + ): Promise { + const [type] = parseAuthorizationHeader(ctx.req.headers.authorization) + switch (type) { + case BEARER: + return this.validateBearerAccessToken(ctx, scopes) + case DPOP: + return this.validateDpopAccessToken(ctx, scopes) + case null: + throw new AuthRequiredError(undefined, 'AuthMissing') + default: + throw new InvalidRequestError( + 'Unexpected authorization type', + 'InvalidToken', + ) + } + } + + async validateDpopAccessToken( + { req, res }: ReqCtx, + scopes: AuthScope[], + ): Promise { + if (!scopes.includes(AuthScope.Access)) { + throw new InvalidRequestError( + 'DPoP access token cannot be used for this request', + 'InvalidToken', + ) + } + + try { + const url = new URL(req.originalUrl || req.url, this._publicUrl) + const result = await this.oauthVerifier.authenticateHttpRequest( + req.method, + url, + req.headers, + { audience: [this.dids.pds] }, + ) + + const { sub } = result.claims + if (typeof sub !== 'string' || !sub.startsWith('did:')) { + throw new InvalidRequestError('Malformed token', 'InvalidToken') + } + + return { + credentials: { + type: 'access', + did: result.claims.sub, + scope: AuthScope.Access, + audience: this.dids.pds, + }, + artifacts: result.token, + } + } catch (err) { + // Make sure to include any WWW-Authenticate header in the response + // (particularly useful for DPoP's "use_dpop_nonce" error) + if (err instanceof WWWAuthenticateError) { + res?.setHeader('WWW-Authenticate', err.wwwAuthenticateHeader) + } + + if (err instanceof OAuthError) { + throw new XRPCError(err.status, err.error_description, err.error) + } + + throw err + } + } + + async validateBearerAccessToken( + { req }: ReqCtx, scopes: AuthScope[], ): Promise { const { did, scope, token, audience } = await this.validateBearerToken( @@ -371,16 +505,63 @@ export class AuthVerifier { return auth.credentials.did === did } } + + protected async jwtVerify( + token: string, + verifyOptions?: jose.JWTVerifyOptions, + ) { + try { + return await jose.jwtVerify(token, this._jwtKey, verifyOptions) + } catch (err) { + if (err?.['code'] === 'ERR_JWT_EXPIRED') { + throw new InvalidRequestError('Token has expired', 'ExpiredToken') + } + throw new InvalidRequestError( + 'Token could not be verified', + 'InvalidToken', + ) + } + } + + protected setAuthHeaders({ req, res }: ReqCtx) { + // Prevent caching (on proxies) of auth dependent responses + res?.setHeader('Cache-Control', 'private') + + // Make sure that browsers do not return cached responses when the auth header changes + res?.appendHeader('Vary', 'Authorization') + + // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2 + if (req.headers.authorization?.startsWith('DPoP')) { + const dpopNonce = this.oauthVerifier.nextDpopNonce() + if (dpopNonce) { + res?.setHeader('DPoP-Nonce', dpopNonce) + res?.appendHeader('Access-Control-Expose-Headers', 'DPoP-Nonce') + } + } + } } // HELPERS // --------- -const BEARER = 'Bearer ' -const BASIC = 'Basic ' +const BASIC = 'Basic' +const BEARER = 'Bearer' +const DPOP = 'DPoP' + +export const parseAuthorizationHeader = (authorization?: string) => { + const result = authorization?.split(' ', 2) + if (result?.length === 2) return result as [type: string, token: string] + return [null] as [type: null] +} + +const isAccessToken = (req: express.Request): boolean => { + const [type] = parseAuthorizationHeader(req.headers.authorization) + return type === BEARER || type === DPOP +} const isBearerToken = (req: express.Request): boolean => { - return req.headers.authorization?.startsWith(BEARER) ?? false + const [type] = parseAuthorizationHeader(req.headers.authorization) + return type === BEARER } const isBasicToken = (req: express.Request): boolean => { @@ -388,42 +569,25 @@ const isBasicToken = (req: express.Request): boolean => { } const bearerTokenFromReq = (req: express.Request) => { - const header = req.headers.authorization || '' - if (!header.startsWith(BEARER)) return null - return header.slice(BEARER.length) -} - -const verifyJwt = async (params: { - key: KeyObject - token: string - verifyOptions?: jose.JWTVerifyOptions -}): Promise => { - const { key, token, verifyOptions } = params - try { - const result = await jose.jwtVerify(token, key, verifyOptions) - return result.payload - } catch (err) { - if (err?.['code'] === 'ERR_JWT_EXPIRED') { - throw new InvalidRequestError('Token has expired', 'ExpiredToken') - } - throw new InvalidRequestError('Token could not be verified', 'InvalidToken') - } + const [type, token] = parseAuthorizationHeader(req.headers.authorization) + return type === BEARER ? token : null } export const parseBasicAuth = ( token: string, ): { username: string; password: string } | null => { - if (!token.startsWith(BASIC)) return null - const b64 = token.slice(BASIC.length) - let parsed: string[] try { - parsed = ui8.toString(ui8.fromString(b64, 'base64pad'), 'utf8').split(':') + const [type, b64] = parseAuthorizationHeader(token) + if (type !== BASIC) return null + const parsed = ui8 + .toString(ui8.fromString(b64, 'base64pad'), 'utf8') + .split(':', 2) + const [username, password] = parsed + if (!username || !password) return null + return { username, password } } catch (err) { return null } - const [username, password] = parsed - if (!username || !password) return null - return { username, password } } const authScopes = new Set(Object.values(AuthScope)) diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index fb6efbebbc6..85789395e98 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -1,6 +1,7 @@ import path from 'node:path' import assert from 'node:assert' import { DAY, HOUR, SECOND } from '@atproto/common' +import { Customization } from '@atproto/oauth-provider' import { ServerEnvironment } from './env' // off-config but still from env: @@ -234,6 +235,105 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { const crawlersCfg: ServerConfig['crawlers'] = env.crawlers ?? [] + const fetchCfg: ServerConfig['fetch'] = { + disableSsrf: env.fetchDisableSsrf ?? false, + } + + const oauthCfg: ServerConfig['oauth'] = entrywayCfg + ? { + issuer: entrywayCfg.url, + provider: false, + } + : { + issuer: serviceCfg.publicUrl, + provider: { + customization: { + name: env.oauthProviderName ?? 'Personal PDS', + logo: env.oauthProviderLogo, + colors: { + primary: env.oauthProviderPrimaryColor, + error: env.oauthProviderErrorColor, + }, + links: [ + { + title: 'Home', + href: env.oauthProviderHomeLink, + rel: 'bookmark', + }, + { + title: 'Terms of Service', + href: env.oauthProviderTosLink, + rel: 'terms-of-service', + }, + { + title: 'Privacy Policy', + href: env.oauthProviderPrivacyPolicyLink, + rel: 'privacy-policy', + }, + { + title: 'Support', + href: env.oauthProviderSupportLink, + rel: 'help', + }, + ].filter( + (f): f is typeof f & { href: NonNullable<(typeof f)['href']> } => + f.href != null, + ), + signIn: { + fields: { + username: { + label: 'Email address or handle', + }, + password: {}, + }, + }, + signUp: { + fields: { + username: { + label: 'Email address', + placeholder: 'Enter your email address', + pattern: + '[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}', + title: 'An email address to identify your account.', + }, + password: { + placeholder: 'Choose your password', + pattern: + // '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^0-9a-zA-Z]).{8,}' + '.{8,}', + title: + // 'A strong password containing at least one uppercase, one lowercase, one number, and one special character, and at least 8 characters long.' + 'A strong password containing at least 8 characters.', + }, + }, + extraFields: { + handle: { + label: 'Enter your user handle', + placeholder: 'e.g. alice', + type: 'text' as const, + pattern: '[a-z0-9-]{3,20}', + }, + birthdate: { + label: 'Birth date', + type: 'date' as const, + required: true, + }, + inviteCode: { + label: 'Invite Code', + type: 'text' as const, + required: invitesCfg.required, + }, + captcha: { + label: 'Are you human?', + type: 'captcha' as const, + required: true, + }, + }, + }, + }, + }, + } + return { service: serviceCfg, db: dbCfg, @@ -251,6 +351,8 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { redis: redisCfg, rateLimits: rateLimitsCfg, crawlers: crawlersCfg, + fetch: fetchCfg, + oauth: oauthCfg, } } @@ -271,6 +373,8 @@ export type ServerConfig = { redis: RedisScratchConfig | null rateLimits: RateLimitsConfig crawlers: string[] + fetch: FetchConfig + oauth: OAuthConfig } export type ServiceConfig = { @@ -337,6 +441,19 @@ export type EntrywayConfig = { plcRotationKey: string } +export type FetchConfig = { + disableSsrf: boolean +} + +export type OAuthConfig = { + issuer: string + provider: + | false + | { + customization: Customization + } +} + export type InvitesConfig = | { required: true diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index e94c84baeb9..a32b60b65c9 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -97,6 +97,7 @@ export const readEnv = (): ServerEnvironment => { crawlers: envList('PDS_CRAWLERS'), // secrets + dpopSecret: envStr('PDS_DPOP_SECRET'), jwtSecret: envStr('PDS_JWT_SECRET'), adminPassword: envStr('PDS_ADMIN_PASSWORD'), @@ -106,6 +107,19 @@ export const readEnv = (): ServerEnvironment => { plcRotationKeyK256PrivateKeyHex: envStr( 'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX', ), + + // fetch + fetchDisableSsrf: envBool('PDS_DISABLE_SSRF'), + + // oauth + oauthProviderName: envStr('PDS_OAUTH_PROVIDER_NAME'), + oauthProviderLogo: envStr('PDS_OAUTH_PROVIDER_LOGO'), + oauthProviderPrimaryColor: envStr('PDS_OAUTH_PROVIDER_PRIMARY_COLOR'), + oauthProviderErrorColor: envStr('PDS_OAUTH_PROVIDER_ERROR_COLOR'), + oauthProviderHomeLink: envStr('PDS_OAUTH_PROVIDER_HOME_LINK'), + oauthProviderTosLink: envStr('PDS_OAUTH_PROVIDER_TOS_LINK'), + oauthProviderPrivacyPolicyLink: envStr('PDS_OAUTH_PROVIDER_POLICY_LINK'), + oauthProviderSupportLink: envStr('PDS_OAUTH_PROVIDER_SUPPORT_LINK'), } } @@ -203,10 +217,24 @@ export type ServerEnvironment = { crawlers?: string[] // secrets + dpopSecret?: string jwtSecret?: string adminPassword?: string // keys plcRotationKeyKmsKeyId?: string plcRotationKeyK256PrivateKeyHex?: string + + // fetch + fetchDisableSsrf?: boolean + + // oauth + oauthProviderName?: string + oauthProviderLogo?: string + oauthProviderPrimaryColor?: string + oauthProviderErrorColor?: string + oauthProviderHomeLink?: string + oauthProviderTosLink?: string + oauthProviderPrivacyPolicyLink?: string + oauthProviderSupportLink?: string } diff --git a/packages/pds/src/config/secrets.ts b/packages/pds/src/config/secrets.ts index eb0f33d182c..d36f95f3869 100644 --- a/packages/pds/src/config/secrets.ts +++ b/packages/pds/src/config/secrets.ts @@ -27,6 +27,7 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => { } return { + dpopSecret: env.dpopSecret, jwtSecret: env.jwtSecret, adminPassword: env.adminPassword, plcRotationKey, @@ -34,6 +35,7 @@ export const envToSecrets = (env: ServerEnvironment): ServerSecrets => { } export type ServerSecrets = { + dpopSecret?: string jwtSecret: string adminPassword: string plcRotationKey: SigningKeyKms | SigningKeyMemory diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index 71be9d76168..23c3730fae7 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -1,4 +1,5 @@ import assert from 'node:assert' + import * as nodemailer from 'nodemailer' import { Redis } from 'ioredis' import * as plc from '@did-plc/lib' @@ -6,18 +7,22 @@ import * as crypto from '@atproto/crypto' import { IdResolver } from '@atproto/identity' import { AtpAgent } from '@atproto/api' import { KmsKeypair, S3BlobStore } from '@atproto/aws' +import { Fetch, safeFetchWrap } from '@atproto-labs/fetch-node' import { RateLimiter, RateLimiterCreator, RateLimiterOpts, createServiceAuthHeaders, } from '@atproto/xrpc-server' +import { JoseKey, JoseKeyset, OAuthVerifier } from '@atproto/oauth-provider' import { ServerConfig, ServerSecrets } from './config' +import { PdsOAuthProvider } from './oauth/provider' import { AuthVerifier, createPublicKeyObject, createSecretKeyObject, } from './auth-verifier' +import { fetchLogger } from './logger' import { ServerMailer } from './mailer' import { ModerationMailer } from './mailer/moderation' import { BlobStore } from '@atproto/repo' @@ -50,6 +55,8 @@ export type AppContextOptions = { moderationAgent?: AtpAgent reportingAgent?: AtpAgent entrywayAgent?: AtpAgent + safeFetch: Fetch + authProvider?: PdsOAuthProvider authVerifier: AuthVerifier plcRotationKey: crypto.Keypair cfg: ServerConfig @@ -74,7 +81,9 @@ export class AppContext { public moderationAgent: AtpAgent | undefined public reportingAgent: AtpAgent | undefined public entrywayAgent: AtpAgent | undefined + public safeFetch: Fetch public authVerifier: AuthVerifier + public authProvider?: PdsOAuthProvider public plcRotationKey: crypto.Keypair public cfg: ServerConfig @@ -97,7 +106,9 @@ export class AppContext { this.moderationAgent = opts.moderationAgent this.reportingAgent = opts.reportingAgent this.entrywayAgent = opts.entrywayAgent + this.safeFetch = opts.safeFetch this.authVerifier = opts.authVerifier + this.authProvider = opts.authProvider this.plcRotationKey = opts.plcRotationKey this.cfg = opts.cfg } @@ -214,20 +225,6 @@ export class AppContext { ) await accountManager.migrateOrThrow() - const jwtKey = cfg.entryway - ? createPublicKeyObject(cfg.entryway.jwtPublicKeyHex) - : jwtSecretKey - - const authVerifier = new AuthVerifier(accountManager, idResolver, { - jwtKey, // @TODO support multiple keys? - adminPass: secrets.adminPassword, - dids: { - pds: cfg.service.did, - entryway: cfg.entryway?.did, - modService: cfg.modService?.did, - }, - }) - const plcRotationKey = secrets.plcRotationKey.provider === 'kms' ? await KmsKeypair.load({ @@ -250,6 +247,69 @@ export class AppContext { appviewCdnUrlPattern: cfg.bskyAppView?.cdnUrlPattern, }) + const keyset = new JoseKeyset([ + await JoseKey.fromKeyLike(jwtSecretKey, 'key-1', 'HS256'), + ]) + + // A Fetch function that protects against SSRF attacks, large responses & + // known bad domains. This function can safely be used to fetch user + // provided URLs (unless "disableSsrf" is true, of course). + const safeFetch = safeFetchWrap({ + allowHttp: cfg.fetch.disableSsrf, + responseMaxSize: 512 * 1024, // 512kB + ssrfProtection: !cfg.fetch.disableSsrf, + fetch: async (request, init?: RequestInit) => { + fetchLogger.info( + { method: init?.method ?? request.method, uri: request.url }, + 'fetch', + ) + return globalThis.fetch(request, init) + }, + }) + + const authProvider = cfg.oauth.provider + ? new PdsOAuthProvider({ + issuer: cfg.oauth.issuer, + keyset, + accountManager, + actorStore, + localViewer, + redis: redisScratch, + dpopSecret: secrets.dpopSecret, + customization: cfg.oauth.provider.customization, + safeFetch, + }) + : undefined + + const oauthVerifier: OAuthVerifier = + authProvider ?? // OAuthProvider is an OAuthVerifier so let's use it + new OAuthVerifier({ + issuer: cfg.oauth.issuer, + keyset, + dpopSecret: secrets.dpopSecret, + redis: redisScratch, + }) + + const jwtKey = cfg.entryway + ? createPublicKeyObject(cfg.entryway.jwtPublicKeyHex) + : jwtSecretKey + + const authVerifier = new AuthVerifier( + accountManager, + idResolver, + oauthVerifier, + { + publicUrl: cfg.service.publicUrl, + jwtKey, + adminPass: secrets.adminPassword, + dids: { + pds: cfg.service.did, + entryway: cfg.entryway?.did, + modService: cfg.modService?.did, + }, + }, + ) + return new AppContext({ actorStore, blobstore, @@ -269,7 +329,9 @@ export class AppContext { moderationAgent, reportingAgent, entrywayAgent, + safeFetch, authVerifier, + authProvider, plcRotationKey, cfg, ...(overrides ?? {}), diff --git a/packages/pds/src/db/cast.ts b/packages/pds/src/db/cast.ts new file mode 100644 index 00000000000..d9ff0f26b4a --- /dev/null +++ b/packages/pds/src/db/cast.ts @@ -0,0 +1,43 @@ +export type DateISO = `${string}T${string}Z` +export const toDateISO = (date: Date): DateISO => date.toISOString() as DateISO +export const fromDateISO = (date: DateISO): Date => new Date(date) + +export type Json = string +export const toJson = (obj: unknown): Json => { + const json = JSON.stringify(obj) + if (typeof json !== 'string') { + throw new TypeError('Invalid JSON') + } + return json as Json +} +export const fromJson = (json: Json): T => { + return JSON.parse(json) as T +} + +export type JsonArray = `[${string}]` +export const toJsonArray = (obj: readonly unknown[]): JsonArray => { + const json = toJson(obj) + if (!json.startsWith('[') || !json.endsWith(']')) { + throw new TypeError('Not a JSON Array') + } + return json as JsonArray +} +export const fromJsonArray = (json: JsonArray): T => { + return fromJson(json) as T +} + +export type JsonObject = `{${string}}` +export const toJsonObject = ( + obj: Readonly>, +): JsonObject => { + const json = toJson(obj) + if (!json.startsWith('{') || !json.endsWith('}')) { + throw new TypeError('Not a JSON Object') + } + return json as JsonObject +} +export const fromJsonObject = >( + json: JsonObject, +): T => { + return fromJson(json) as T +} diff --git a/packages/pds/src/db/index.ts b/packages/pds/src/db/index.ts index 2ccf49b90c7..23a50b0210b 100644 --- a/packages/pds/src/db/index.ts +++ b/packages/pds/src/db/index.ts @@ -1,3 +1,4 @@ export * from './db' +export * from './cast.js' export * from './migrator' export * from './util' diff --git a/packages/pds/src/error.ts b/packages/pds/src/error.ts index a4de90f580e..b1033b26b3c 100644 --- a/packages/pds/src/error.ts +++ b/packages/pds/src/error.ts @@ -1,12 +1,19 @@ import { XRPCError } from '@atproto/xrpc-server' import { ErrorRequestHandler } from 'express' import { httpLogger as log } from './logger' +import { OAuthError } from '@atproto/oauth-provider' export const handler: ErrorRequestHandler = (err, _req, res, next) => { log.error(err, 'unexpected internal server error') if (res.headersSent) { return next(err) } + + if (err instanceof OAuthError) { + res.status(err.status).json(err.toJSON()) + return + } + const serverError = XRPCError.fromError(err) res.status(serverError.type).json(serverError.payload) } diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index 4f7c57c1652..0d239c53969 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -11,6 +11,7 @@ import events from 'events' import { Options as XrpcServerOptions } from '@atproto/xrpc-server' import { DAY, HOUR, MINUTE } from '@atproto/common' import API from './api' +import * as authRoutes from './auth-routes' import * as basicRoutes from './basic-routes' import * as wellKnown from './well-known' import * as error from './error' @@ -98,6 +99,7 @@ export class PDS { server = API(server, ctx) + app.use(authRoutes.createRouter(ctx)) app.use(basicRoutes.createRouter(ctx)) app.use(wellKnown.createRouter(ctx)) app.use(server.xrpc.router) diff --git a/packages/pds/src/logger.ts b/packages/pds/src/logger.ts index 737e05a5e67..c48ea9fa657 100644 --- a/packages/pds/src/logger.ts +++ b/packages/pds/src/logger.ts @@ -13,6 +13,8 @@ export const mailerLogger = subsystemLogger('pds:mailer') export const labelerLogger = subsystemLogger('pds:labeler') export const crawlerLogger = subsystemLogger('pds:crawler') export const httpLogger = subsystemLogger('pds') +export const fetchLogger = subsystemLogger('pds:fetch') +export const oauthLogger = subsystemLogger('pds:oauth') export const loggerMiddleware = pinoHttp({ logger: httpLogger, diff --git a/packages/pds/src/oauth/detailed-account-store.ts b/packages/pds/src/oauth/detailed-account-store.ts new file mode 100644 index 00000000000..a5171d76844 --- /dev/null +++ b/packages/pds/src/oauth/detailed-account-store.ts @@ -0,0 +1,96 @@ +import { + AccountInfo, + AccountStore, + DeviceId, + LoginCredentials, +} from '@atproto/oauth-provider' + +import { AccountManager } from '../account-manager/index.js' +import { ActorStore } from '../actor-store/index.js' +import { ProfileViewBasic } from '../lexicon/types/app/bsky/actor/defs.js' +import { LocalViewerCreator } from '../read-after-write/index.js' + +/** + * Although the {@link AccountManager} class implements the {@link AccountStore} + * interface, the accounts it returns do not contain any profile information + * (display name, avatar, etc). This is due to the fact that the account manager + * does not have access to the account's repos. The {@link DetailedAccountStore} + * is a wrapper around the {@link AccountManager} that enriches the accounts + * with profile information using the account's repos through the + * {@link ActorStore}. + */ +export class DetailedAccountStore implements AccountStore { + constructor( + private accountManager: AccountManager, + private actorStore: ActorStore, + private localViewer: LocalViewerCreator, + ) {} + + private async getProfile(did: string): Promise { + // TODO: Should we cache this? + return this.actorStore.read(did, async (actorStoreReader) => { + const localViewer = this.localViewer(actorStoreReader) + return localViewer.getProfileBasic() + }) + } + + private async enrichAccountInfo( + accountInfo: AccountInfo, + ): Promise { + const { account } = accountInfo + if (!account.picture || !account.name) { + const profile = await this.getProfile(account.sub) + if (profile) { + account.picture ||= profile.avatar + account.name ||= profile.displayName + } + } + + return accountInfo + } + + async authenticateAccount( + credentials: LoginCredentials, + deviceId: DeviceId, + ): Promise { + const accountInfo = await this.accountManager.authenticateAccount( + credentials, + deviceId, + ) + if (!accountInfo) return null + return this.enrichAccountInfo(accountInfo) + } + + async addAuthorizedClient( + deviceId: DeviceId, + sub: string, + clientId: string, + ): Promise { + return this.accountManager.addAuthorizedClient(deviceId, sub, clientId) + } + + async getDeviceAccount( + deviceId: DeviceId, + sub: string, + ): Promise { + const accountInfo = await this.accountManager.getDeviceAccount( + deviceId, + sub, + ) + if (!accountInfo) return null + return this.enrichAccountInfo(accountInfo) + } + + async listDeviceAccounts(deviceId: DeviceId): Promise { + const accountInfos = await this.accountManager.listDeviceAccounts(deviceId) + return Promise.all( + accountInfos.map(async (accountInfo) => + this.enrichAccountInfo(accountInfo), + ), + ) + } + + async removeDeviceAccount(deviceId: DeviceId, sub: string): Promise { + return this.accountManager.removeDeviceAccount(deviceId, sub) + } +} diff --git a/packages/pds/src/oauth/provider.ts b/packages/pds/src/oauth/provider.ts new file mode 100644 index 00000000000..d1954e1c06b --- /dev/null +++ b/packages/pds/src/oauth/provider.ts @@ -0,0 +1,82 @@ +import { Fetch } from '@atproto-labs/fetch-node' +import { + AccessTokenType, + Customization, + DpopManagerOptions, + Keyset, + OAuthProvider, +} from '@atproto/oauth-provider' +import { Redis, RedisOptions } from 'ioredis' + +import { AccountManager } from '../account-manager/index.js' +import { ActorStore } from '../actor-store/index.js' +import { oauthLogger } from '../logger.js' +import { LocalViewerCreator } from '../read-after-write/index.js' +import { DetailedAccountStore } from './detailed-account-store.js' + +export type AuthProviderOptions = { + issuer: string + keyset: Keyset + accountManager: AccountManager + actorStore: ActorStore + localViewer: LocalViewerCreator + redis?: Redis | RedisOptions | string + dpopSecret?: DpopManagerOptions['dpopSecret'] + customization?: Customization + safeFetch: Fetch +} + +export class PdsOAuthProvider extends OAuthProvider { + private customization?: Customization + + constructor({ + accountManager, + actorStore, + localViewer, + keyset, + redis, + dpopSecret, + issuer, + customization, + safeFetch, + }: AuthProviderOptions) { + super({ + issuer, + keyset, + dpopSecret, + redis, + safeFetch, + + accountStore: new DetailedAccountStore( + accountManager, + actorStore, + localViewer, + ), + requestStore: accountManager, + deviceStore: accountManager, + tokenStore: accountManager, + + // If the PDS is both an authorization server & resource server (no + // entryway), there is no need to use JWTs as access tokens. Instead, + // the PDS can use tokenId as access tokens. This allows the PDS to + // always use up-to-date token data from the token store. + accessTokenType: AccessTokenType.id, + + // TODO: make client client list configurable + onIsFirstPartyClient: (client) => client.id === 'https://bsky.app/', + }) + + this.customization = customization + } + + createRouter() { + return this.httpHandler({ + customization: this.customization, + + // Log oauth provider errors using our own logger + onError: (req, res, err) => { + oauthLogger.error({ err }, 'oauth-provider error') + }, + }) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29677719104..f18d743ff99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -950,7 +950,7 @@ importers: version: 18.3.1 react-native: specifier: '*' - version: 0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1) + version: 0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.24.5)(react@18.3.1) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -1199,6 +1199,9 @@ importers: packages/pds: dependencies: + '@atproto-labs/fetch-node': + specifier: workspace:* + version: link:../internal/fetch-node '@atproto/api': specifier: workspace:^ version: link:../api @@ -1217,6 +1220,9 @@ importers: '@atproto/lexicon': specifier: workspace:^ version: link:../lexicon + '@atproto/oauth-provider': + specifier: workspace:^ + version: link:../oauth/oauth-provider '@atproto/repo': specifier: workspace:^ version: link:../repo @@ -4089,24 +4095,42 @@ packages: semver: 6.3.1 dev: false - /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.18.6): + /@babel/helper-create-class-features-plugin@7.24.5(@babel/core@7.24.5): + resolution: {integrity: sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.24.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.5 + semver: 6.3.1 + dev: false + + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.5): resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 dev: false - /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.18.6): + /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.5): resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.22.10 '@babel/helper-plugin-utils': 7.24.5 debug: 4.3.4 @@ -4223,13 +4247,13 @@ packages: engines: {node: '>=6.9.0'} dev: false - /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.18.6): + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.5): resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.24.5 @@ -4247,6 +4271,18 @@ packages: '@babel/helper-optimise-call-expression': 7.22.5 dev: false + /@babel/helper-replace-supers@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.24.5 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: false + /@babel/helper-simple-access@7.22.5: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} @@ -4370,62 +4406,62 @@ packages: '@babel/types': 7.24.5 dev: false - /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.5(@babel/core@7.18.6): + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.18.6): + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.18.6): + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.24.5) dev: false - /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.18.6): + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.18.6): + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.24.5): resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.5) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) dev: false /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.18.6): @@ -4440,27 +4476,39 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: false - /@babel/plugin-proposal-export-default-from@7.24.1(@babel/core@7.18.6): + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.24.5): + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-proposal-export-default-from@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.24.5) dev: false - /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.18.6): + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.24.5): resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) dev: false /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.18.6): @@ -4475,19 +4523,31 @@ packages: '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) dev: false - /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.18.6): + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.24.5): + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) + dev: false + + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.24.5): resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) dev: false - /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.18.6): + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.24.5): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. @@ -4495,23 +4555,23 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/compat-data': 7.22.9 - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.22.10 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.24.5) dev: false - /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.18.6): + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.24.5): resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) dev: false /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.18.6): @@ -4527,13 +4587,26 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) dev: false - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.18.6): + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.24.5): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) + dev: false + + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.5): resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 dev: false /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.18.6): @@ -4543,6 +4616,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.5): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} @@ -4560,42 +4643,52 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.18.6): + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.5): resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.18.6): + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.22.5 dev: false - /@babel/plugin-syntax-export-default-from@7.24.1(@babel/core@7.18.6): + /@babel/plugin-syntax-export-default-from@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.18.6): + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.5): resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false @@ -4609,23 +4702,33 @@ packages: '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.18.6): + /@babel/plugin-syntax-flow@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.18.6): + /@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false @@ -4636,6 +4739,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.5): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} @@ -4644,6 +4757,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.5): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.18.6): resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} @@ -4655,6 +4778,16 @@ packages: '@babel/helper-plugin-utils': 7.24.5 dev: false + /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.18.6): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -4662,6 +4795,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.5): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -4671,6 +4814,15 @@ packages: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.5): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.18.6): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -4678,6 +4830,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.5): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} @@ -4686,6 +4848,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.5): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} @@ -4694,6 +4866,16 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.5): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.18.6): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -4703,13 +4885,22 @@ packages: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.18.6): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.5): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.5): resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false @@ -4721,6 +4912,17 @@ packages: dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.5): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: false /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.18.6): resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} @@ -4742,185 +4944,195 @@ packages: '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.18.6): + /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.5): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.18.6): + /@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.5): resolution: {integrity: sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.5) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.24.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-block-scoping@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-block-scoping@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-class-static-block@7.24.4(@babel/core@7.18.6): + /@babel/plugin-transform-class-static-block@7.24.4(@babel/core@7.24.5): resolution: {integrity: sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-classes@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-classes@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.24.5 - '@babel/helper-replace-supers': 7.24.1(@babel/core@7.18.6) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5) '@babel/helper-split-export-declaration': 7.24.5 globals: 11.12.0 dev: false - /@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 '@babel/template': 7.24.0 dev: false - /@babel/plugin-transform-destructuring@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-destructuring@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.5) dev: false /@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.18.6): @@ -4934,79 +5146,90 @@ packages: '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) dev: false - /@babel/plugin-transform-for-of@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.5) + dev: false + + /@babel/plugin-transform-for-of@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 dev: false - /@babel/plugin-transform-function-name@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-function-name@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-literals@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-literals@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false @@ -5022,294 +5245,306 @@ packages: '@babel/helper-simple-access': 7.22.5 dev: false - /@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.24.5): + resolution: {integrity: sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-simple-access': 7.22.5 + dev: false + + /@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-validator-identifier': 7.24.5 dev: false - /@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.18.6): + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.5): resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.22.5 dev: false - /@babel/plugin-transform-new-target@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-new-target@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-object-rest-spread@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-object-rest-spread@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-object-super@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-object-super@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/helper-replace-supers': 7.24.1(@babel/core@7.18.6) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-optional-chaining@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-optional-chaining@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-parameters@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-parameters@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-private-property-in-object@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-private-property-in-object@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.5) dev: false - /@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-react-display-name@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-react-display-name@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-react-jsx-self@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-react-jsx-self@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.18.6): + /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.24.5): resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.5) '@babel/types': 7.24.5 dev: false - /@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 regenerator-transform: 0.15.2 dev: false - /@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-runtime@7.24.3(@babel/core@7.18.6): + /@babel/plugin-transform-runtime@7.24.3(@babel/core@7.24.5): resolution: {integrity: sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.24.5 - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.18.6) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.18.6) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.18.6) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.5) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.5) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.5) semver: 6.3.1 transitivePeerDependencies: - supports-color dev: false - /@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-spread@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-spread@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 dev: false - /@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-typeof-symbol@7.24.5(@babel/core@7.18.6): + /@babel/plugin-transform-typeof-symbol@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false @@ -5326,135 +5561,148 @@ packages: '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.18.6) dev: false - /@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-typescript@7.24.5(@babel/core@7.24.5): + resolution: {integrity: sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.5) + dev: false + + /@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.18.6): + /@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.24.5): resolution: {integrity: sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.24.5 dev: false - /@babel/preset-env@7.24.5(@babel/core@7.18.6): + /@babel/preset-env@7.24.5(@babel/core@7.24.5): resolution: {integrity: sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/compat-data': 7.24.4 - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.5 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.18.6) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.18.6) - '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.18.6) - '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.18.6) - '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.6) - '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-object-rest-spread': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-typeof-symbol': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.18.6) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.18.6) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.18.6) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.18.6) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.18.6) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.5) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.5) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.5) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.5) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.5) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.5) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.24.5) + '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.5) + '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-object-rest-spread': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-typeof-symbol': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.5) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.5) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.5) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.5) core-js-compat: 3.37.1 semver: 6.3.1 transitivePeerDependencies: @@ -5473,14 +5721,14 @@ packages: '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.18.6) dev: false - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.18.6): + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.5): resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.18.6 + '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.5 - '@babel/types': 7.22.10 + '@babel/types': 7.24.5 esutils: 2.0.3 dev: false @@ -7005,54 +7253,54 @@ packages: - supports-color dev: false - /@react-native/babel-preset@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + /@react-native/babel-preset@0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5): resolution: {integrity: sha512-KJuu3XyVh3qgyUer+rEqh9a/JoUxsDOzkJNfRpDyXiAyjDRoVch60X/Xa/NcEQ93iCVHAWs0yQ+XGNGIBCYE6g==} engines: {node: '>=18'} peerDependencies: '@babel/core': '*' dependencies: - '@babel/core': 7.18.6 - '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.18.6) - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.18.6) - '@babel/plugin-proposal-export-default-from': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.18.6) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.6) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.18.6) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.18.6) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.18.6) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.18.6) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.6) - '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-react-display-name': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.18.6) - '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.18.6) - '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.18.6) - '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.18.6) - '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.24.5) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.24.5) + '@babel/plugin-proposal-export-default-from': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.24.5) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.24.5) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.24.5) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.24.5) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.24.5) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.24.5) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.5) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-react-display-name': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.24.5) + '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.24.5) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.5) '@babel/template': 7.22.5 '@react-native/babel-plugin-codegen': 0.74.83(@babel/preset-env@7.24.5) - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.18.6) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.24.5) react-refresh: 0.14.2 transitivePeerDependencies: - '@babel/preset-env' @@ -7066,7 +7314,7 @@ packages: '@babel/preset-env': ^7.1.6 dependencies: '@babel/parser': 7.22.10 - '@babel/preset-env': 7.24.5(@babel/core@7.18.6) + '@babel/preset-env': 7.24.5(@babel/core@7.24.5) glob: 7.2.3 hermes-parser: 0.19.1 invariant: 2.2.4 @@ -7077,14 +7325,14 @@ packages: - supports-color dev: false - /@react-native/community-cli-plugin@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + /@react-native/community-cli-plugin@0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5): resolution: {integrity: sha512-7GAFjFOg1mFSj8bnFNQS4u8u7+QtrEeflUIDVZGEfBZQ3wMNI5ycBzbBGycsZYiq00Xvoc6eKFC7kvIaqeJpUQ==} engines: {node: '>=18'} dependencies: '@react-native-community/cli-server-api': 13.6.6 '@react-native-community/cli-tools': 13.6.6 '@react-native/dev-middleware': 0.74.83 - '@react-native/metro-babel-transformer': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + '@react-native/metro-babel-transformer': 0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5) chalk: 4.1.2 execa: 5.1.1 metro: 0.80.9 @@ -7141,14 +7389,14 @@ packages: engines: {node: '>=18'} dev: false - /@react-native/metro-babel-transformer@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + /@react-native/metro-babel-transformer@0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5): resolution: {integrity: sha512-hGdx5N8diu8y+GW/ED39vTZa9Jx1di2ZZ0aapbhH4egN1agIAusj5jXTccfNBwwWF93aJ5oVbRzfteZgjbutKg==} engines: {node: '>=18'} peerDependencies: '@babel/core': '*' dependencies: - '@babel/core': 7.18.6 - '@react-native/babel-preset': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + '@babel/core': 7.24.5 + '@react-native/babel-preset': 0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5) hermes-parser: 0.19.1 nullthrows: 1.1.1 transitivePeerDependencies: @@ -7174,7 +7422,7 @@ packages: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.3.1 - react-native: 0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1) + react-native: 0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.24.5)(react@18.3.1) dev: false /@rnx-kit/chromium-edge-launcher@1.0.0: @@ -8475,46 +8723,46 @@ packages: '@types/babel__traverse': 7.20.1 dev: true - /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.18.6): + /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.5): resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: '@babel/compat-data': 7.22.9 - '@babel/core': 7.18.6 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.5) semver: 6.3.1 transitivePeerDependencies: - supports-color dev: false - /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.18.6): + /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.5): resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.5) core-js-compat: 3.37.1 transitivePeerDependencies: - supports-color dev: false - /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.18.6): + /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.5): resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.18.6 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + '@babel/core': 7.24.5 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.5) transitivePeerDependencies: - supports-color dev: false - /babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.18.6): + /babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.24.5): resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} dependencies: - '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.5) transitivePeerDependencies: - '@babel/core' dev: false @@ -11976,7 +12224,7 @@ packages: '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.6) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.18.6) '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) - '@babel/preset-env': 7.24.5(@babel/core@7.18.6) + '@babel/preset-env': 7.24.5(@babel/core@7.24.5) '@babel/preset-flow': 7.24.1(@babel/core@7.18.6) '@babel/preset-typescript': 7.24.1(@babel/core@7.18.6) '@babel/register': 7.23.7(@babel/core@7.18.6) @@ -14215,7 +14463,7 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - /react-native@0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1): + /react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.24.5)(react@18.3.1): resolution: {integrity: sha512-0H2XpmghwOtfPpM2LKqHIN7gxy+7G/r1hwJHKLV6uoyXGC/gCojRtoo5NqyKrWpFC8cqyT6wTYCLuG7CxEKilg==} engines: {node: '>=18'} hasBin: true @@ -14232,7 +14480,7 @@ packages: '@react-native-community/cli-platform-ios': 13.6.6 '@react-native/assets-registry': 0.74.83 '@react-native/codegen': 0.74.83(@babel/preset-env@7.24.5) - '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.24.5)(@babel/preset-env@7.24.5) '@react-native/gradle-plugin': 0.74.83 '@react-native/js-polyfills': 0.74.83 '@react-native/normalize-colors': 0.74.83