@atproto/oauth-types 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +12 -0
- package/LICENSE.txt +7 -0
- package/README.md +3 -0
- package/dist/access-token.d.ts +4 -0
- package/dist/access-token.d.ts.map +1 -0
- package/dist/access-token.js +6 -0
- package/dist/access-token.js.map +1 -0
- package/dist/atproto-loopback-client-metadata.d.ts +3 -0
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -0
- package/dist/atproto-loopback-client-metadata.js +26 -0
- package/dist/atproto-loopback-client-metadata.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-authentication-request-parameters.d.ts +128 -0
- package/dist/oauth-authentication-request-parameters.d.ts.map +1 -0
- package/dist/oauth-authentication-request-parameters.js +76 -0
- package/dist/oauth-authentication-request-parameters.js.map +1 -0
- package/dist/oauth-authorization-details.d.ts +54 -0
- package/dist/oauth-authorization-details.d.ts.map +1 -0
- package/dist/oauth-authorization-details.js +20 -0
- package/dist/oauth-authorization-details.js.map +1 -0
- package/dist/oauth-authorization-server-metadata.d.ts +428 -0
- package/dist/oauth-authorization-server-metadata.d.ts.map +1 -0
- package/dist/oauth-authorization-server-metadata.js +88 -0
- package/dist/oauth-authorization-server-metadata.js.map +1 -0
- package/dist/oauth-client-credentials.d.ts +66 -0
- package/dist/oauth-client-credentials.d.ts.map +1 -0
- package/dist/oauth-client-credentials.js +30 -0
- package/dist/oauth-client-credentials.js.map +1 -0
- package/dist/oauth-client-id-discoverable.d.ts +8 -0
- package/dist/oauth-client-id-discoverable.d.ts.map +1 -0
- package/dist/oauth-client-id-discoverable.js +48 -0
- package/dist/oauth-client-id-discoverable.js.map +1 -0
- package/dist/oauth-client-id-loopback.d.ts +5 -0
- package/dist/oauth-client-id-loopback.d.ts.map +1 -0
- package/dist/oauth-client-id-loopback.js +44 -0
- package/dist/oauth-client-id-loopback.js.map +1 -0
- package/dist/oauth-client-id-url.d.ts +3 -0
- package/dist/oauth-client-id-url.d.ts.map +1 -0
- package/dist/oauth-client-id-url.js +21 -0
- package/dist/oauth-client-id-url.js.map +1 -0
- package/dist/oauth-client-id.d.ts +4 -0
- package/dist/oauth-client-id.d.ts.map +1 -0
- package/dist/oauth-client-id.js +6 -0
- package/dist/oauth-client-id.js.map +1 -0
- package/dist/oauth-client-identification.d.ts +31 -0
- package/dist/oauth-client-identification.d.ts.map +1 -0
- package/dist/oauth-client-identification.js +12 -0
- package/dist/oauth-client-identification.js.map +1 -0
- package/dist/oauth-client-metadata.d.ts +1576 -0
- package/dist/oauth-client-metadata.d.ts.map +1 -0
- package/dist/oauth-client-metadata.js +70 -0
- package/dist/oauth-client-metadata.js.map +1 -0
- package/dist/oauth-endpoint-auth-method.d.ts +4 -0
- package/dist/oauth-endpoint-auth-method.d.ts.map +1 -0
- package/dist/oauth-endpoint-auth-method.js +14 -0
- package/dist/oauth-endpoint-auth-method.js.map +1 -0
- package/dist/oauth-endpoint-name.d.ts +2 -0
- package/dist/oauth-endpoint-name.d.ts.map +1 -0
- package/dist/oauth-endpoint-name.js +3 -0
- package/dist/oauth-endpoint-name.js.map +1 -0
- package/dist/oauth-grant-type.d.ts +4 -0
- package/dist/oauth-grant-type.d.ts.map +1 -0
- package/dist/oauth-grant-type.js +14 -0
- package/dist/oauth-grant-type.js.map +1 -0
- package/dist/oauth-issuer-identifier.d.ts +3 -0
- package/dist/oauth-issuer-identifier.d.ts.map +1 -0
- package/dist/oauth-issuer-identifier.js +59 -0
- package/dist/oauth-issuer-identifier.js.map +1 -0
- package/dist/oauth-par-response.d.ts +10 -0
- package/dist/oauth-par-response.d.ts.map +1 -0
- package/dist/oauth-par-response.js +8 -0
- package/dist/oauth-par-response.js.map +1 -0
- package/dist/oauth-protected-resource-metadata.d.ts +90 -0
- package/dist/oauth-protected-resource-metadata.d.ts.map +1 -0
- package/dist/oauth-protected-resource-metadata.js +75 -0
- package/dist/oauth-protected-resource-metadata.js.map +1 -0
- package/dist/oauth-response-mode.d.ts +4 -0
- package/dist/oauth-response-mode.d.ts.map +1 -0
- package/dist/oauth-response-mode.js +10 -0
- package/dist/oauth-response-mode.js.map +1 -0
- package/dist/oauth-response-type.d.ts +4 -0
- package/dist/oauth-response-type.d.ts.map +1 -0
- package/dist/oauth-response-type.js +17 -0
- package/dist/oauth-response-type.js.map +1 -0
- package/dist/oauth-token-response.d.ts +103 -0
- package/dist/oauth-token-response.d.ts.map +1 -0
- package/dist/oauth-token-response.js +26 -0
- package/dist/oauth-token-response.js.map +1 -0
- package/dist/oauth-token-type.d.ts +4 -0
- package/dist/oauth-token-type.d.ts.map +1 -0
- package/dist/oauth-token-type.js +16 -0
- package/dist/oauth-token-type.js.map +1 -0
- package/dist/oidc-claims-parameter.d.ts +4 -0
- package/dist/oidc-claims-parameter.d.ts.map +1 -0
- package/dist/oidc-claims-parameter.js +36 -0
- package/dist/oidc-claims-parameter.js.map +1 -0
- package/dist/oidc-claims-properties.d.ts +16 -0
- package/dist/oidc-claims-properties.d.ts.map +1 -0
- package/dist/oidc-claims-properties.js +11 -0
- package/dist/oidc-claims-properties.js.map +1 -0
- package/dist/oidc-entity-type.d.ts +4 -0
- package/dist/oidc-entity-type.d.ts.map +1 -0
- package/dist/oidc-entity-type.js +6 -0
- package/dist/oidc-entity-type.js.map +1 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +23 -0
- package/dist/util.js.map +1 -0
- package/package.json +37 -0
- package/src/access-token.ts +4 -0
- package/src/atproto-loopback-client-metadata.ts +30 -0
- package/src/constants.ts +9 -0
- package/src/index.ts +27 -0
- package/src/oauth-authentication-request-parameters.ts +104 -0
- package/src/oauth-authorization-details.ts +28 -0
- package/src/oauth-authorization-server-metadata.ts +106 -0
- package/src/oauth-client-credentials.ts +34 -0
- package/src/oauth-client-id-discoverable.ts +66 -0
- package/src/oauth-client-id-loopback.ts +58 -0
- package/src/oauth-client-id-url.ts +25 -0
- package/src/oauth-client-id.ts +4 -0
- package/src/oauth-client-identification.ts +14 -0
- package/src/oauth-client-metadata.ts +75 -0
- package/src/oauth-endpoint-auth-method.ts +13 -0
- package/src/oauth-endpoint-name.ts +5 -0
- package/src/oauth-grant-type.ts +13 -0
- package/src/oauth-issuer-identifier.ts +61 -0
- package/src/oauth-par-response.ts +7 -0
- package/src/oauth-protected-resource-metadata.ts +85 -0
- package/src/oauth-response-mode.ts +9 -0
- package/src/oauth-response-type.ts +17 -0
- package/src/oauth-token-response.ts +29 -0
- package/src/oauth-token-type.ts +15 -0
- package/src/oidc-claims-parameter.ts +40 -0
- package/src/oidc-claims-properties.ts +11 -0
- package/src/oidc-entity-type.ts +5 -0
- package/src/util.ts +20 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oauth-token-response.js","sourceRoot":"","sources":["../src/oauth-token-response.ts"],"names":[],"mappings":";;;AAAA,sCAA8C;AAC9C,6BAAuB;AAEvB,qFAAkF;AAClF,+DAA4D;AAE5D;;GAEG;AACU,QAAA,wBAAwB,GAAG,OAAC;KACtC,MAAM,CAAC;IACN,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;IACxB,UAAU,EAAE,0CAAoB;IAChC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,qBAAe,CAAC,QAAQ,EAAE;IACpC,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,qBAAqB,EAAE,gEAA+B,CAAC,QAAQ,EAAE;CAClE,CAAC;IACF,0DAA0D;IAC1D,qEAAqE;KACpE,WAAW,EAAE,CAAA"}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
export declare const oauthTokenTypeSchema: z.ZodUnion<[z.ZodEffects<z.ZodString, "DPoP", string>, z.ZodEffects<z.ZodString, "Bearer", string>]>;
|
3
|
+
export type OAuthTokenType = z.infer<typeof oauthTokenTypeSchema>;
|
4
|
+
//# sourceMappingURL=oauth-token-type.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oauth-token-type.d.ts","sourceRoot":"","sources":["../src/oauth-token-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,oBAAoB,sGAS/B,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA"}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.oauthTokenTypeSchema = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
// Case insensitive input, normalized output
|
6
|
+
exports.oauthTokenTypeSchema = zod_1.z.union([
|
7
|
+
zod_1.z
|
8
|
+
.string()
|
9
|
+
.regex(/^DPoP$/i)
|
10
|
+
.transform(() => 'DPoP'),
|
11
|
+
zod_1.z
|
12
|
+
.string()
|
13
|
+
.regex(/^Bearer$/i)
|
14
|
+
.transform(() => 'Bearer'),
|
15
|
+
]);
|
16
|
+
//# sourceMappingURL=oauth-token-type.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oauth-token-type.js","sourceRoot":"","sources":["../src/oauth-token-type.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AAEvB,4CAA4C;AAC/B,QAAA,oBAAoB,GAAG,OAAC,CAAC,KAAK,CAAC;IAC1C,OAAC;SACE,MAAM,EAAE;SACR,KAAK,CAAC,SAAS,CAAC;SAChB,SAAS,CAAC,GAAG,EAAE,CAAC,MAAe,CAAC;IACnC,OAAC;SACE,MAAM,EAAE;SACR,KAAK,CAAC,WAAW,CAAC;SAClB,SAAS,CAAC,GAAG,EAAE,CAAC,QAAiB,CAAC;CACtC,CAAC,CAAA"}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
export declare const oidcClaimsParameterSchema: z.ZodEnum<["auth_time", "nonce", "acr", "name", "family_name", "given_name", "middle_name", "nickname", "preferred_username", "gender", "picture", "profile", "website", "birthdate", "zoneinfo", "locale", "updated_at", "email", "email_verified", "phone_number", "phone_number_verified", "address"]>;
|
3
|
+
export type OidcClaimsParameter = z.infer<typeof oidcClaimsParameterSchema>;
|
4
|
+
//# sourceMappingURL=oidc-claims-parameter.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-claims-parameter.d.ts","sourceRoot":"","sources":["../src/oidc-claims-parameter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,yBAAyB,2SAmCpC,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA"}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.oidcClaimsParameterSchema = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
exports.oidcClaimsParameterSchema = zod_1.z.enum([
|
6
|
+
// https://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html#rfc.section.5.2
|
7
|
+
// if client metadata "require_auth_time" is true, this *must* be provided
|
8
|
+
'auth_time',
|
9
|
+
// OIDC
|
10
|
+
'nonce',
|
11
|
+
'acr',
|
12
|
+
// OpenID: "profile" scope
|
13
|
+
'name',
|
14
|
+
'family_name',
|
15
|
+
'given_name',
|
16
|
+
'middle_name',
|
17
|
+
'nickname',
|
18
|
+
'preferred_username',
|
19
|
+
'gender',
|
20
|
+
'picture',
|
21
|
+
'profile',
|
22
|
+
'website',
|
23
|
+
'birthdate',
|
24
|
+
'zoneinfo',
|
25
|
+
'locale',
|
26
|
+
'updated_at',
|
27
|
+
// OpenID: "email" scope
|
28
|
+
'email',
|
29
|
+
'email_verified',
|
30
|
+
// OpenID: "phone" scope
|
31
|
+
'phone_number',
|
32
|
+
'phone_number_verified',
|
33
|
+
// OpenID: "address" scope
|
34
|
+
'address',
|
35
|
+
]);
|
36
|
+
//# sourceMappingURL=oidc-claims-parameter.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-claims-parameter.js","sourceRoot":"","sources":["../src/oidc-claims-parameter.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AAEV,QAAA,yBAAyB,GAAG,OAAC,CAAC,IAAI,CAAC;IAC9C,oGAAoG;IACpG,0EAA0E;IAC1E,WAAW;IAEX,OAAO;IACP,OAAO;IACP,KAAK;IAEL,0BAA0B;IAC1B,MAAM;IACN,aAAa;IACb,YAAY;IACZ,aAAa;IACb,UAAU;IACV,oBAAoB;IACpB,QAAQ;IACR,SAAS;IACT,SAAS;IACT,SAAS;IACT,WAAW;IACX,UAAU;IACV,QAAQ;IACR,YAAY;IAEZ,wBAAwB;IACxB,OAAO;IACP,gBAAgB;IAEhB,wBAAwB;IACxB,cAAc;IACd,uBAAuB;IAEvB,0BAA0B;IAC1B,SAAS;CACV,CAAC,CAAA"}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
export declare const oidcClaimsPropertiesSchema: z.ZodObject<{
|
3
|
+
essential: z.ZodOptional<z.ZodBoolean>;
|
4
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
5
|
+
values: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, "many">>;
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
7
|
+
values?: (string | number | boolean)[] | undefined;
|
8
|
+
value?: string | number | boolean | undefined;
|
9
|
+
essential?: boolean | undefined;
|
10
|
+
}, {
|
11
|
+
values?: (string | number | boolean)[] | undefined;
|
12
|
+
value?: string | number | boolean | undefined;
|
13
|
+
essential?: boolean | undefined;
|
14
|
+
}>;
|
15
|
+
export type OidcClaimsProperties = z.infer<typeof oidcClaimsPropertiesSchema>;
|
16
|
+
//# sourceMappingURL=oidc-claims-properties.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-claims-properties.d.ts","sourceRoot":"","sources":["../src/oidc-claims-properties.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,0BAA0B;;;;;;;;;;;;EAIrC,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA"}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.oidcClaimsPropertiesSchema = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
const oidcClaimsValueSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean()]);
|
6
|
+
exports.oidcClaimsPropertiesSchema = zod_1.z.object({
|
7
|
+
essential: zod_1.z.boolean().optional(),
|
8
|
+
value: oidcClaimsValueSchema.optional(),
|
9
|
+
values: zod_1.z.array(oidcClaimsValueSchema).optional(),
|
10
|
+
});
|
11
|
+
//# sourceMappingURL=oidc-claims-properties.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-claims-properties.js","sourceRoot":"","sources":["../src/oidc-claims-properties.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AAEvB,MAAM,qBAAqB,GAAG,OAAC,CAAC,KAAK,CAAC,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AAE/D,QAAA,0BAA0B,GAAG,OAAC,CAAC,MAAM,CAAC;IACjD,SAAS,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,KAAK,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IACvC,MAAM,EAAE,OAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;CAClD,CAAC,CAAA"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-entity-type.d.ts","sourceRoot":"","sources":["../src/oidc-entity-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,oBAAoB,qCAAmC,CAAA;AAEpE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA"}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.oidcEntityTypeSchema = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
exports.oidcEntityTypeSchema = zod_1.z.enum(['userinfo', 'id_token']);
|
6
|
+
//# sourceMappingURL=oidc-entity-type.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oidc-entity-type.js","sourceRoot":"","sources":["../src/oidc-entity-type.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AAEV,QAAA,oBAAoB,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA"}
|
package/dist/util.d.ts
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
export declare function isIP(hostname: string): boolean;
|
2
|
+
export type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]';
|
3
|
+
export declare function isLoopbackHost(host: unknown): host is LoopbackHost;
|
4
|
+
export declare function isLoopbackUrl(input: URL | string): boolean;
|
5
|
+
//# sourceMappingURL=util.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,WAQpC;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAA;AAE9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,YAAY,CAElE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAG1D"}
|
package/dist/util.js
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.isLoopbackUrl = exports.isLoopbackHost = exports.isIP = void 0;
|
4
|
+
function isIP(hostname) {
|
5
|
+
// IPv4
|
6
|
+
if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/))
|
7
|
+
return true;
|
8
|
+
// IPv6
|
9
|
+
if (hostname.startsWith('[') && hostname.endsWith(']'))
|
10
|
+
return true;
|
11
|
+
return false;
|
12
|
+
}
|
13
|
+
exports.isIP = isIP;
|
14
|
+
function isLoopbackHost(host) {
|
15
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
|
16
|
+
}
|
17
|
+
exports.isLoopbackHost = isLoopbackHost;
|
18
|
+
function isLoopbackUrl(input) {
|
19
|
+
const url = typeof input === 'string' ? new URL(input) : input;
|
20
|
+
return isLoopbackHost(url.hostname);
|
21
|
+
}
|
22
|
+
exports.isLoopbackUrl = isLoopbackUrl;
|
23
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAA,SAAgB,IAAI,CAAC,QAAgB;IACnC,OAAO;IACP,IAAI,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC;QAAE,OAAO,IAAI,CAAA;IAEvD,OAAO;IACP,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAEnE,OAAO,KAAK,CAAA;AACd,CAAC;AARD,oBAQC;AAID,SAAgB,cAAc,CAAC,IAAa;IAC1C,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,OAAO,CAAA;AACzE,CAAC;AAFD,wCAEC;AAED,SAAgB,aAAa,CAAC,KAAmB;IAC/C,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9D,OAAO,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AACrC,CAAC;AAHD,sCAGC"}
|
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "@atproto/oauth-types",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"license": "MIT",
|
5
|
+
"description": "OAuth typing & validation library",
|
6
|
+
"keywords": [
|
7
|
+
"atproto",
|
8
|
+
"oauth",
|
9
|
+
"types",
|
10
|
+
"isomorphic"
|
11
|
+
],
|
12
|
+
"homepage": "https://atproto.com",
|
13
|
+
"repository": {
|
14
|
+
"type": "git",
|
15
|
+
"url": "https://github.com/bluesky-social/atproto",
|
16
|
+
"directory": "packages/oauth/oauth-types"
|
17
|
+
},
|
18
|
+
"type": "commonjs",
|
19
|
+
"main": "dist/index.js",
|
20
|
+
"types": "dist/index.d.ts",
|
21
|
+
"exports": {
|
22
|
+
".": {
|
23
|
+
"types": "./dist/index.d.ts",
|
24
|
+
"default": "./dist/index.js"
|
25
|
+
}
|
26
|
+
},
|
27
|
+
"dependencies": {
|
28
|
+
"zod": "^3.23.8",
|
29
|
+
"@atproto/jwk": "0.1.0"
|
30
|
+
},
|
31
|
+
"devDependencies": {
|
32
|
+
"typescript": "^5.3.3"
|
33
|
+
},
|
34
|
+
"scripts": {
|
35
|
+
"build": "tsc --build tsconfig.build.json"
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { isOAuthClientIdLoopback } from './oauth-client-id-loopback.js'
|
2
|
+
import { OAuthClientMetadataInput } from './oauth-client-metadata.js'
|
3
|
+
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
4
|
+
|
5
|
+
export function atprotoLoopbackClientMetadata(
|
6
|
+
clientId: string,
|
7
|
+
): OAuthClientMetadataInput {
|
8
|
+
if (!isOAuthClientIdLoopback(clientId)) {
|
9
|
+
throw new TypeError(`Invalid loopback client ID ${clientId}`)
|
10
|
+
}
|
11
|
+
|
12
|
+
const { origin, pathname, searchParams } = parseOAuthClientIdUrl(clientId)
|
13
|
+
|
14
|
+
return {
|
15
|
+
client_id: clientId,
|
16
|
+
client_name: 'Loopback client',
|
17
|
+
response_types: ['code id_token', 'code'],
|
18
|
+
grant_types: ['authorization_code', 'implicit', 'refresh_token'],
|
19
|
+
scope: 'openid profile offline_access',
|
20
|
+
redirect_uris: searchParams.has('redirect_uri')
|
21
|
+
? (searchParams.getAll('redirect_uri') as [string, ...string[]])
|
22
|
+
: (['127.0.0.1', '[::1]'].map(
|
23
|
+
(ip) =>
|
24
|
+
Object.assign(new URL(pathname, origin), { hostname: ip }).href,
|
25
|
+
) as [string, ...string[]]),
|
26
|
+
token_endpoint_auth_method: 'none',
|
27
|
+
application_type: 'native',
|
28
|
+
dpop_bound_access_tokens: true,
|
29
|
+
}
|
30
|
+
}
|
package/src/constants.ts
ADDED
package/src/index.ts
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
export * from './constants.js'
|
2
|
+
export * from './util.js'
|
3
|
+
|
4
|
+
export * from './access-token.js'
|
5
|
+
export * from './atproto-loopback-client-metadata.js'
|
6
|
+
export * from './oauth-client-id-discoverable.js'
|
7
|
+
export * from './oauth-client-id-loopback.js'
|
8
|
+
export * from './oauth-authentication-request-parameters.js'
|
9
|
+
export * from './oauth-authorization-details.js'
|
10
|
+
export * from './oauth-authorization-server-metadata.js'
|
11
|
+
export * from './oauth-client-credentials.js'
|
12
|
+
export * from './oauth-client-id.js'
|
13
|
+
export * from './oauth-client-identification.js'
|
14
|
+
export * from './oauth-client-metadata.js'
|
15
|
+
export * from './oauth-endpoint-auth-method.js'
|
16
|
+
export * from './oauth-endpoint-name.js'
|
17
|
+
export * from './oauth-grant-type.js'
|
18
|
+
export * from './oauth-issuer-identifier.js'
|
19
|
+
export * from './oauth-par-response.js'
|
20
|
+
export * from './oauth-protected-resource-metadata.js'
|
21
|
+
export * from './oauth-response-mode.js'
|
22
|
+
export * from './oauth-response-type.js'
|
23
|
+
export * from './oauth-token-response.js'
|
24
|
+
export * from './oauth-token-type.js'
|
25
|
+
export * from './oidc-claims-parameter.js'
|
26
|
+
export * from './oidc-claims-properties.js'
|
27
|
+
export * from './oidc-entity-type.js'
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import { signedJwtSchema } from '@atproto/jwk'
|
2
|
+
import { z } from 'zod'
|
3
|
+
|
4
|
+
import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js'
|
5
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
6
|
+
import { oidcClaimsParameterSchema } from './oidc-claims-parameter.js'
|
7
|
+
import { oidcClaimsPropertiesSchema } from './oidc-claims-properties.js'
|
8
|
+
import { oidcEntityTypeSchema } from './oidc-entity-type.js'
|
9
|
+
|
10
|
+
/**
|
11
|
+
* @see {@link https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest | OIDC}
|
12
|
+
*/
|
13
|
+
export const oauthAuthenticationRequestParametersSchema = z.object({
|
14
|
+
client_id: oauthClientIdSchema,
|
15
|
+
|
16
|
+
state: z.string().optional(),
|
17
|
+
nonce: z.string().optional(),
|
18
|
+
dpop_jkt: z.string().optional(),
|
19
|
+
|
20
|
+
response_type: z.enum([
|
21
|
+
// OAuth2 (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-4.1.1)
|
22
|
+
'code',
|
23
|
+
'token',
|
24
|
+
|
25
|
+
// OIDC (https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)
|
26
|
+
'id_token',
|
27
|
+
'none',
|
28
|
+
'code token',
|
29
|
+
'code id_token',
|
30
|
+
'id_token token',
|
31
|
+
'code id_token token',
|
32
|
+
]),
|
33
|
+
|
34
|
+
// Default depend on response_type
|
35
|
+
response_mode: z.enum(['query', 'fragment', 'form_post']).optional(),
|
36
|
+
|
37
|
+
// PKCE
|
38
|
+
code_challenge: z.string().optional(),
|
39
|
+
code_challenge_method: z.enum(['S256', 'plain']).default('S256').optional(),
|
40
|
+
|
41
|
+
redirect_uri: z.string().url().optional(),
|
42
|
+
|
43
|
+
// email profile openid (other?)
|
44
|
+
scope: z
|
45
|
+
.string()
|
46
|
+
.regex(/^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$/)
|
47
|
+
.optional(),
|
48
|
+
|
49
|
+
// OIDC
|
50
|
+
|
51
|
+
// Specifies the allowable elapsed time in seconds since the last time the
|
52
|
+
// End-User was actively authenticated by the OP. If the elapsed time is
|
53
|
+
// greater than this value, the OP MUST attempt to actively re-authenticate
|
54
|
+
// the End-User. (The max_age request parameter corresponds to the OpenID 2.0
|
55
|
+
// PAPE [OpenID.PAPE] max_auth_age request parameter.) When max_age is used,
|
56
|
+
// the ID Token returned MUST include an auth_time Claim Value. Note that
|
57
|
+
// max_age=0 is equivalent to prompt=login.
|
58
|
+
max_age: z.number().int().min(0).optional(),
|
59
|
+
|
60
|
+
claims: z
|
61
|
+
.record(
|
62
|
+
oidcEntityTypeSchema,
|
63
|
+
z.record(
|
64
|
+
oidcClaimsParameterSchema,
|
65
|
+
z.union([z.literal(null), oidcClaimsPropertiesSchema]),
|
66
|
+
),
|
67
|
+
)
|
68
|
+
.optional(),
|
69
|
+
|
70
|
+
// https://openid.net/specs/openid-connect-core-1_0.html#RegistrationParameter
|
71
|
+
// Not supported by this library (yet?)
|
72
|
+
// registration: clientMetadataSchema.optional(),
|
73
|
+
|
74
|
+
login_hint: z.string().min(1).optional(),
|
75
|
+
|
76
|
+
ui_locales: z
|
77
|
+
.string()
|
78
|
+
.regex(/^[a-z]{2}(-[A-Z]{2})?( [a-z]{2}(-[A-Z]{2})?)*$/) // fr-CA fr en
|
79
|
+
.optional(),
|
80
|
+
|
81
|
+
// Previous ID Token, should be provided when prompt=none is used
|
82
|
+
id_token_hint: signedJwtSchema.optional(),
|
83
|
+
|
84
|
+
// Type of UI the AS is displayed on
|
85
|
+
display: z.enum(['page', 'popup', 'touch']).optional(),
|
86
|
+
|
87
|
+
/**
|
88
|
+
* - "none" will only be allowed if the user already allowed the client on the same device
|
89
|
+
* - "login" will force the user to login again, unless he very recently logged in
|
90
|
+
* - "consent" will force the user to consent again
|
91
|
+
* - "select_account" will force the user to select an account
|
92
|
+
*/
|
93
|
+
prompt: z.enum(['none', 'login', 'consent', 'select_account']).optional(),
|
94
|
+
|
95
|
+
// https://datatracker.ietf.org/doc/html/rfc9396
|
96
|
+
authorization_details: oauthAuthorizationDetailsSchema.optional(),
|
97
|
+
})
|
98
|
+
|
99
|
+
/**
|
100
|
+
* @see {oauthAuthenticationRequestParametersSchema}
|
101
|
+
*/
|
102
|
+
export type OAuthAuthenticationRequestParameters = z.infer<
|
103
|
+
typeof oauthAuthenticationRequestParametersSchema
|
104
|
+
>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396#section-2 | RFC 9396, Section 2}
|
5
|
+
*/
|
6
|
+
export const oauthAuthorizationDetailSchema = z.object({
|
7
|
+
type: z.string(),
|
8
|
+
locations: z.array(z.string().url()).optional(),
|
9
|
+
actions: z.array(z.string()).optional(),
|
10
|
+
datatypes: z.array(z.string()).optional(),
|
11
|
+
identifier: z.string().optional(),
|
12
|
+
privileges: z.array(z.string()).optional(),
|
13
|
+
})
|
14
|
+
|
15
|
+
export type OAuthAuthorizationDetail = z.infer<
|
16
|
+
typeof oauthAuthorizationDetailSchema
|
17
|
+
>
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396#section-2 | RFC 9396, Section 2}
|
21
|
+
*/
|
22
|
+
export const oauthAuthorizationDetailsSchema = z.array(
|
23
|
+
oauthAuthorizationDetailSchema,
|
24
|
+
)
|
25
|
+
|
26
|
+
export type OAuthAuthorizationDetails = z.infer<
|
27
|
+
typeof oauthAuthorizationDetailsSchema
|
28
|
+
>
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc8414}
|
7
|
+
*/
|
8
|
+
export const oauthAuthorizationServerMetadataSchema = z.object({
|
9
|
+
issuer: oauthIssuerIdentifierSchema,
|
10
|
+
|
11
|
+
claims_supported: z.array(z.string()).optional(),
|
12
|
+
claims_locales_supported: z.array(z.string()).optional(),
|
13
|
+
claims_parameter_supported: z.boolean().optional(),
|
14
|
+
request_parameter_supported: z.boolean().optional(),
|
15
|
+
request_uri_parameter_supported: z.boolean().optional(),
|
16
|
+
require_request_uri_registration: z.boolean().optional(),
|
17
|
+
scopes_supported: z.array(z.string()).optional(),
|
18
|
+
subject_types_supported: z.array(z.string()).optional(),
|
19
|
+
response_types_supported: z.array(z.string()).optional(),
|
20
|
+
response_modes_supported: z.array(z.string()).optional(),
|
21
|
+
grant_types_supported: z.array(z.string()).optional(),
|
22
|
+
code_challenge_methods_supported: z.array(z.string()).min(1).optional(),
|
23
|
+
ui_locales_supported: z.array(z.string()).optional(),
|
24
|
+
id_token_signing_alg_values_supported: z.array(z.string()).optional(),
|
25
|
+
display_values_supported: z.array(z.string()).optional(),
|
26
|
+
request_object_signing_alg_values_supported: z.array(z.string()).optional(),
|
27
|
+
authorization_response_iss_parameter_supported: z.boolean().optional(),
|
28
|
+
authorization_details_types_supported: z.array(z.string()).optional(),
|
29
|
+
request_object_encryption_alg_values_supported: z
|
30
|
+
.array(z.string())
|
31
|
+
.optional(),
|
32
|
+
request_object_encryption_enc_values_supported: z
|
33
|
+
.array(z.string())
|
34
|
+
.optional(),
|
35
|
+
|
36
|
+
jwks_uri: z.string().url().optional(),
|
37
|
+
|
38
|
+
authorization_endpoint: z.string().url(), // .optional(),
|
39
|
+
|
40
|
+
token_endpoint: z.string().url(), // .optional(),
|
41
|
+
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
42
|
+
token_endpoint_auth_signing_alg_values_supported: z
|
43
|
+
.array(z.string())
|
44
|
+
.optional(),
|
45
|
+
|
46
|
+
revocation_endpoint: z.string().url().optional(),
|
47
|
+
revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
48
|
+
revocation_endpoint_auth_signing_alg_values_supported: z
|
49
|
+
.array(z.string())
|
50
|
+
.optional(),
|
51
|
+
|
52
|
+
introspection_endpoint: z.string().url().optional(),
|
53
|
+
introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
54
|
+
introspection_endpoint_auth_signing_alg_values_supported: z
|
55
|
+
.array(z.string())
|
56
|
+
.optional(),
|
57
|
+
|
58
|
+
pushed_authorization_request_endpoint: z.string().url().optional(),
|
59
|
+
pushed_authorization_request_endpoint_auth_methods_supported: z
|
60
|
+
.array(z.string())
|
61
|
+
.optional(),
|
62
|
+
pushed_authorization_request_endpoint_auth_signing_alg_values_supported: z
|
63
|
+
.array(z.string())
|
64
|
+
.optional(),
|
65
|
+
|
66
|
+
require_pushed_authorization_requests: z.boolean().optional(),
|
67
|
+
|
68
|
+
userinfo_endpoint: z.string().url().optional(),
|
69
|
+
end_session_endpoint: z.string().url().optional(),
|
70
|
+
registration_endpoint: z.string().url().optional(),
|
71
|
+
|
72
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#section-5.1
|
73
|
+
dpop_signing_alg_values_supported: z.array(z.string()).optional(),
|
74
|
+
|
75
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
|
76
|
+
protected_resources: z.array(z.string().url()).optional(),
|
77
|
+
})
|
78
|
+
|
79
|
+
export type OAuthAuthorizationServerMetadata = z.infer<
|
80
|
+
typeof oauthAuthorizationServerMetadataSchema
|
81
|
+
>
|
82
|
+
|
83
|
+
export const oauthAuthorizationServerMetadataValidator =
|
84
|
+
oauthAuthorizationServerMetadataSchema
|
85
|
+
.superRefine((data, ctx) => {
|
86
|
+
if (
|
87
|
+
data.require_pushed_authorization_requests &&
|
88
|
+
!data.pushed_authorization_request_endpoint
|
89
|
+
) {
|
90
|
+
ctx.addIssue({
|
91
|
+
code: z.ZodIssueCode.custom,
|
92
|
+
message:
|
93
|
+
'"pushed_authorization_request_endpoint" required when "require_pushed_authorization_requests" is true',
|
94
|
+
})
|
95
|
+
}
|
96
|
+
})
|
97
|
+
.superRefine((data, ctx) => {
|
98
|
+
if (data.response_types_supported) {
|
99
|
+
if (!data.response_types_supported.includes('code')) {
|
100
|
+
ctx.addIssue({
|
101
|
+
code: z.ZodIssueCode.custom,
|
102
|
+
message: 'Response type "code" is required',
|
103
|
+
})
|
104
|
+
}
|
105
|
+
}
|
106
|
+
})
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
import { signedJwtSchema } from '@atproto/jwk'
|
3
|
+
|
4
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
5
|
+
import { CLIENT_ASSERTION_TYPE_JWT_BEARER } from './constants.js'
|
6
|
+
|
7
|
+
export const oauthClientCredentialsJwtBearerSchema = z.object({
|
8
|
+
client_id: oauthClientIdSchema,
|
9
|
+
client_assertion_type: z.literal(CLIENT_ASSERTION_TYPE_JWT_BEARER),
|
10
|
+
/**
|
11
|
+
* - "sub" the subject MUST be the "client_id" of the OAuth client
|
12
|
+
* - "iat" is required and MUST be less than one minute
|
13
|
+
* - "aud" must containing a value that identifies the authorization server
|
14
|
+
* - The JWT MAY contain a "jti" (JWT ID) claim that provides a unique identifier for the token.
|
15
|
+
* - Note that the authorization server may reject JWTs with an "exp" claim value that is unreasonably far in the future.
|
16
|
+
*
|
17
|
+
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
|
18
|
+
*/
|
19
|
+
client_assertion: signedJwtSchema,
|
20
|
+
})
|
21
|
+
|
22
|
+
export const oauthClientCredentialsSecretPostSchema = z.object({
|
23
|
+
client_id: oauthClientIdSchema,
|
24
|
+
client_secret: z.string(),
|
25
|
+
})
|
26
|
+
|
27
|
+
export const oauthClientCredentialsSchema = z.union([
|
28
|
+
oauthClientCredentialsJwtBearerSchema,
|
29
|
+
oauthClientCredentialsSecretPostSchema,
|
30
|
+
])
|
31
|
+
|
32
|
+
export type OAuthClientCredentials = z.infer<
|
33
|
+
typeof oauthClientCredentialsSchema
|
34
|
+
>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
2
|
+
import { OAuthClientId } from './oauth-client-id.js'
|
3
|
+
import { isIP } from './util.js'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
|
7
|
+
*/
|
8
|
+
export type OAuthClientIdDiscoverable = OAuthClientId & `https://${string}`
|
9
|
+
|
10
|
+
export function isOAuthClientIdDiscoverable<C extends OAuthClientId>(
|
11
|
+
clientId: C,
|
12
|
+
): clientId is C & OAuthClientIdDiscoverable {
|
13
|
+
try {
|
14
|
+
parseOAuthDiscoverableClientId(clientId)
|
15
|
+
return true
|
16
|
+
} catch {
|
17
|
+
return false
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
export function parseOAuthDiscoverableClientId(clientId: OAuthClientId): URL {
|
22
|
+
const url = parseOAuthClientIdUrl(clientId)
|
23
|
+
|
24
|
+
// Optimization: cheap checks first
|
25
|
+
|
26
|
+
if (url.hostname === 'localhost') {
|
27
|
+
throw new TypeError('ClientID must not be a loopback hostname')
|
28
|
+
}
|
29
|
+
|
30
|
+
if (url.protocol !== 'https:') {
|
31
|
+
throw new TypeError('ClientID must use the "https:" protocol')
|
32
|
+
}
|
33
|
+
|
34
|
+
if (url.hash) {
|
35
|
+
throw new TypeError('ClientID must not contain a fragment')
|
36
|
+
}
|
37
|
+
|
38
|
+
if (url.username || url.password) {
|
39
|
+
throw new TypeError('ClientID must not contain credentials')
|
40
|
+
}
|
41
|
+
|
42
|
+
if (url.pathname === '/') {
|
43
|
+
throw new TypeError(
|
44
|
+
'ClientID must contain a path (e.g. "/client-metadata")',
|
45
|
+
)
|
46
|
+
}
|
47
|
+
|
48
|
+
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
|
49
|
+
throw new TypeError('ClientID must not end with a trailing slash')
|
50
|
+
}
|
51
|
+
|
52
|
+
if (url.pathname.includes('//')) {
|
53
|
+
throw new TypeError(
|
54
|
+
`ClientID must not contain any double slashes in its path`,
|
55
|
+
)
|
56
|
+
}
|
57
|
+
|
58
|
+
// Note: Query string is allowed
|
59
|
+
// Note: no restriction on the port for non-loopback URIs
|
60
|
+
|
61
|
+
if (isIP(url.hostname)) {
|
62
|
+
throw new TypeError('ClientID must not be an IP address')
|
63
|
+
}
|
64
|
+
|
65
|
+
return url
|
66
|
+
}
|