Improve login_hint
handling (#3933)
* Return atproto handle in identity resolution result * Use resolved handle or did instead of raw input as "login_hint" * Normalize and validate `login_hint` in oauth request properties
This commit is contained in:
parent
4e96e2c7b7
commit
192f3ab89c
5
.changeset/angry-garlics-destroy.md
Normal file
5
.changeset/angry-garlics-destroy.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@atproto/oauth-client": patch
|
||||
---
|
||||
|
||||
Use resolved handle or did instead of raw input as "login_hint"
|
5
.changeset/blue-rivers-care.md
Normal file
5
.changeset/blue-rivers-care.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@atproto/oauth-provider": patch
|
||||
---
|
||||
|
||||
Normalize and validate `login_hint` in oauth request properties
|
5
.changeset/cyan-rocks-listen.md
Normal file
5
.changeset/cyan-rocks-listen.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@atproto-labs/identity-resolver": patch
|
||||
---
|
||||
|
||||
Return atproto handle in identity resolution result
|
@ -1,4 +1,4 @@
|
||||
import { normalizeAndEnsureValidHandle } from '@atproto/syntax'
|
||||
import { isValidHandle, normalizeAndEnsureValidHandle } from '@atproto/syntax'
|
||||
import {
|
||||
Did,
|
||||
DidDocument,
|
||||
@ -17,6 +17,7 @@ import {
|
||||
export type ResolvedIdentity = {
|
||||
did: NonNullable<ResolvedHandle>
|
||||
pds: URL
|
||||
handle?: string
|
||||
}
|
||||
|
||||
export type ResolveIdentityOptions = ResolveDidOptions & ResolveHandleOptions
|
||||
@ -49,6 +50,7 @@ export class IdentityResolver {
|
||||
return {
|
||||
did: document.id,
|
||||
pds: new URL(service.serviceEndpoint),
|
||||
handle: extractHandle(document),
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +91,21 @@ export class IdentityResolver {
|
||||
}
|
||||
}
|
||||
|
||||
function extractHandle(
|
||||
document: DidDocument<AtprotoIdentityDidMethods>,
|
||||
): string | undefined {
|
||||
if (document.alsoKnownAs) {
|
||||
for (const h of document.alsoKnownAs) {
|
||||
if (h.startsWith('at://')) {
|
||||
const handle = h.slice(5)
|
||||
if (isValidHandle(handle)) return handle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isAtprotoPersonalDataServerService<M extends string>(
|
||||
this: DidDocument<M>,
|
||||
s: DidService,
|
||||
|
@ -307,9 +307,7 @@ export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
|
||||
code_challenge: pkce.challenge,
|
||||
code_challenge_method: pkce.method,
|
||||
state,
|
||||
login_hint: identity
|
||||
? input // If input is a handle or a DID, use it as a login_hint
|
||||
: undefined,
|
||||
login_hint: identity?.handle ?? identity?.did,
|
||||
response_mode: this.responseMode,
|
||||
response_type: 'code' as const,
|
||||
scope: options?.scope ?? this.clientMetadata.scope,
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@atproto-labs/simple-store": "workspace:*",
|
||||
"@atproto-labs/simple-store-memory": "workspace:*",
|
||||
"@atproto/common": "workspace:^",
|
||||
"@atproto/did": "workspace:*",
|
||||
"@atproto/jwk": "workspace:*",
|
||||
"@atproto/jwk-jose": "workspace:*",
|
||||
"@atproto/oauth-types": "workspace:*",
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { isAtprotoDid } from '@atproto/did'
|
||||
import type { Account } from '@atproto/oauth-provider-api'
|
||||
import {
|
||||
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
||||
OAuthAuthorizationRequestParameters,
|
||||
OAuthAuthorizationServerMetadata,
|
||||
} from '@atproto/oauth-types'
|
||||
import { isValidHandle } from '@atproto/syntax'
|
||||
import { ClientAuth } from '../client/client-auth.js'
|
||||
import { ClientId } from '../client/client-id.js'
|
||||
import { Client } from '../client/client.js'
|
||||
@ -305,6 +307,24 @@ export class RequestManager {
|
||||
parameters = { ...parameters, prompt: 'consent' }
|
||||
}
|
||||
|
||||
// atproto extension: ensure that the login_hint is a valid handle or DID
|
||||
// @NOTE we to allow invalid case here, which is not spec'd anywhere.
|
||||
const hint = parameters.login_hint?.toLowerCase()
|
||||
if (hint) {
|
||||
if (!isAtprotoDid(hint) && !isValidHandle(hint)) {
|
||||
throw new InvalidParametersError(
|
||||
parameters,
|
||||
`Invalid login_hint "${hint}"`,
|
||||
)
|
||||
}
|
||||
|
||||
// @TODO: ensure that the account actually exists on this server (there is
|
||||
// no point in showing the UI to the user if the account does not exist).
|
||||
|
||||
// Update the parameters to ensure the right case is used
|
||||
parameters = { ...parameters, login_hint: hint }
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
|
153
pnpm-lock.yaml
generated
153
pnpm-lock.yaml
generated
@ -58,7 +58,7 @@ importers:
|
||||
version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5)
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
node-gyp:
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1
|
||||
@ -110,7 +110,7 @@ importers:
|
||||
version: link:../lex-cli
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
prettier:
|
||||
specifier: ^3.2.5
|
||||
version: 3.2.5
|
||||
@ -319,10 +319,10 @@ importers:
|
||||
version: 6.9.7
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
ts-node:
|
||||
specifier: ^10.8.2
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -377,10 +377,10 @@ importers:
|
||||
version: 5.1.1
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
ts-node:
|
||||
specifier: ^10.8.2
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -408,7 +408,7 @@ importers:
|
||||
devDependencies:
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -433,7 +433,7 @@ importers:
|
||||
devDependencies:
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -455,7 +455,7 @@ importers:
|
||||
version: link:../common
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -541,7 +541,7 @@ importers:
|
||||
version: 0.2.24(@swc/core@1.11.29)
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -572,7 +572,7 @@ importers:
|
||||
version: 6.1.2
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -784,7 +784,7 @@ importers:
|
||||
devDependencies:
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1037,6 +1037,9 @@ importers:
|
||||
'@atproto/common':
|
||||
specifier: workspace:^
|
||||
version: link:../../common
|
||||
'@atproto/did':
|
||||
specifier: workspace:*
|
||||
version: link:../../did
|
||||
'@atproto/jwk':
|
||||
specifier: workspace:*
|
||||
version: link:../jwk
|
||||
@ -1399,10 +1402,10 @@ importers:
|
||||
version: 6.9.7
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
ts-node:
|
||||
specifier: ^10.8.2
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1577,13 +1580,13 @@ importers:
|
||||
version: 6.1.2
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
puppeteer:
|
||||
specifier: ^23.5.2
|
||||
version: 23.5.3(typescript@5.8.2)
|
||||
ts-node:
|
||||
specifier: ^10.8.2
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1623,7 +1626,7 @@ importers:
|
||||
devDependencies:
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1663,7 +1666,7 @@ importers:
|
||||
version: 8.5.4
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1672,7 +1675,7 @@ importers:
|
||||
devDependencies:
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.8.2
|
||||
@ -1746,7 +1749,7 @@ importers:
|
||||
version: 6.1.2
|
||||
jest:
|
||||
specifier: ^28.1.2
|
||||
version: 28.1.2(@types/node@18.19.67)
|
||||
version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
jose:
|
||||
specifier: ^4.15.4
|
||||
version: 4.15.4
|
||||
@ -8584,12 +8587,6 @@ packages:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/node@22.15.27:
|
||||
resolution: {integrity: sha512-5fF+eu5mwihV2BeVtX5vijhdaZOfkQTATrePEaXTcKqI16LhJ7gi2/Vhd9OZM0UojcdmiOCVg5rrax+i1MdoQQ==}
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
dev: true
|
||||
|
||||
/@types/nodemailer@6.4.6:
|
||||
resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==}
|
||||
dependencies:
|
||||
@ -13161,7 +13158,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-cli@28.1.3(@types/node@18.19.67):
|
||||
/jest-cli@28.1.3(@types/node@18.19.67)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
hasBin: true
|
||||
@ -13189,34 +13186,6 @@ packages:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/jest-cli@28.1.3(@types/node@22.15.27)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
|
||||
peerDependenciesMeta:
|
||||
node-notifier:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@jest/core': 28.1.3(ts-node@10.8.2)
|
||||
'@jest/test-result': 28.1.3
|
||||
'@jest/types': 28.1.3
|
||||
chalk: 4.1.2
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
import-local: 3.1.0
|
||||
jest-config: 28.1.3(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
jest-util: 28.1.3
|
||||
jest-validate: 28.1.3
|
||||
prompts: 2.4.2
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/jest-config@28.1.3(@types/node@18.19.67)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
@ -13252,47 +13221,7 @@ packages:
|
||||
pretty-format: 28.1.3
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-config@28.1.3(@types/node@22.15.27)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.18.6
|
||||
'@jest/test-sequencer': 28.1.3
|
||||
'@jest/types': 28.1.3
|
||||
'@types/node': 22.15.27
|
||||
babel-jest: 28.1.3(@babel/core@7.18.6)
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 28.1.3
|
||||
jest-environment-node: 28.1.3
|
||||
jest-get-type: 28.0.2
|
||||
jest-regex-util: 28.0.2
|
||||
jest-resolve: 28.1.3
|
||||
jest-runner: 28.1.3
|
||||
jest-util: 28.1.3
|
||||
jest-validate: 28.1.3
|
||||
micromatch: 4.0.5
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 28.1.3
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2)
|
||||
ts-node: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@ -13605,7 +13534,7 @@ packages:
|
||||
supports-color: 8.1.1
|
||||
dev: true
|
||||
|
||||
/jest@28.1.2(@types/node@18.19.67):
|
||||
/jest@28.1.2(@types/node@18.19.67)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
hasBin: true
|
||||
@ -13618,27 +13547,7 @@ packages:
|
||||
'@jest/core': 28.1.3(ts-node@10.8.2)
|
||||
'@jest/types': 28.1.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 28.1.3(@types/node@18.19.67)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/jest@28.1.2(@types/node@22.15.27)(ts-node@10.8.2):
|
||||
resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
|
||||
peerDependenciesMeta:
|
||||
node-notifier:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@jest/core': 28.1.3(ts-node@10.8.2)
|
||||
'@jest/types': 28.1.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 28.1.3(@types/node@22.15.27)(ts-node@10.8.2)
|
||||
jest-cli: 28.1.3(@types/node@18.19.67)(ts-node@10.8.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- supports-color
|
||||
@ -17429,7 +17338,7 @@ packages:
|
||||
code-block-writer: 13.0.3
|
||||
dev: false
|
||||
|
||||
/ts-node@10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2):
|
||||
/ts-node@10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2):
|
||||
resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -17449,7 +17358,7 @@ packages:
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 22.15.27
|
||||
'@types/node': 18.19.67
|
||||
acorn: 8.10.0
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
@ -17746,10 +17655,6 @@ packages:
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
/undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
dev: true
|
||||
|
||||
/undici@5.28.4:
|
||||
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
||||
engines: {node: '>=14.0'}
|
||||
|
Loading…
x
Reference in New Issue
Block a user