Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@atproto/oauth-types

Package Overview
Dependencies
Maintainers
0
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@atproto/oauth-types - npm Package Compare versions

Comparing version 0.1.4 to 0.1.5

dist/oauth-access-token.d.ts

26

CHANGELOG.md
# @atproto/oauth-types
## 0.1.5
### Patch Changes
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Properly validate client metadata scope
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow ClientID query params to end with a slash "/" char
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Expose OAuthScope
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - add assertion utils for client ids
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow loopback client ids to omit the (empty) path parameter
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Enforce ClientID URL path to be normalized
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Rename OAuthAuthenticationRequestParameters to OAuthAuthorizationRequestParameters
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Restrict the value used as code_challenge_methods_supported
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add missing "expires_in" property to OAuthParResponse type definition
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow loopback clients to define their scopes through the "scope" client_id query parameter.
- [#2755](https://github.com/bluesky-social/atproto/pull/2755) [`ed325d863`](https://github.com/bluesky-social/atproto/commit/ed325d863ce8ea5986c5a45c3188aaa35288b7a8) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Improve error description in case of invalid loopback client_id
## 0.1.4

@@ -4,0 +30,0 @@

17

dist/atproto-loopback-client-metadata.js

@@ -5,22 +5,11 @@ "use strict";

const oauth_client_id_loopback_js_1 = require("./oauth-client-id-loopback.js");
const oauth_client_id_url_js_1 = require("./oauth-client-id-url.js");
function atprotoLoopbackClientMetadata(clientId) {
if (!(0, oauth_client_id_loopback_js_1.isOAuthClientIdLoopback)(clientId)) {
throw new TypeError(`Invalid loopback client ID ${clientId}`);
}
const { origin, pathname, searchParams } = (0, oauth_client_id_url_js_1.parseOAuthClientIdUrl)(clientId);
for (const name of searchParams.keys()) {
if (name !== 'redirect_uri') {
throw new TypeError(`Invalid query parameter ${name} in client ID`);
}
}
const redirectUris = searchParams.getAll('redirect_uri');
const { scope = 'atproto', redirect_uris = [`http://127.0.0.1/`, `http://[::1]/`], } = (0, oauth_client_id_loopback_js_1.parseOAuthLoopbackClientId)(clientId);
return {
client_id: clientId,
scope,
redirect_uris,
client_name: 'Loopback client',
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
redirect_uris: (redirectUris.length
? redirectUris
: ['127.0.0.1', '[::1]'].map((ip) => Object.assign(new URL(pathname, origin), { hostname: ip }).href)),
token_endpoint_auth_method: 'none',

@@ -27,0 +16,0 @@ application_type: 'native',

export * from './constants.js';
export * from './util.js';
export * from './access-token.js';
export * from './atproto-loopback-client-metadata.js';
export * from './oauth-client-id-discoverable.js';
export * from './oauth-client-id-loopback.js';
export * from './oauth-authentication-request-parameters.js';
export * from './oauth-access-token.js';
export * from './oauth-authorization-code-grant-token-request.js';
export * from './oauth-authorization-details.js';
export * from './oauth-authorization-request-jar.js';
export * from './oauth-authorization-request-par.js';
export * from './oauth-authorization-request-parameters.js';
export * from './oauth-authorization-request-query.js';
export * from './oauth-authorization-request-uri.js';
export * from './oauth-authorization-server-metadata.js';
export * from './oauth-client-credentials-grant-token-request.js';
export * from './oauth-client-credentials.js';
export * from './oauth-client-id-discoverable.js';
export * from './oauth-client-id-loopback.js';
export * from './oauth-client-id.js';
export * from './oauth-client-identification.js';
export * from './oauth-client-metadata.js';

@@ -17,7 +22,15 @@ export * from './oauth-endpoint-auth-method.js';

export * from './oauth-grant-type.js';
export * from './oauth-introspection-response.js';
export * from './oauth-issuer-identifier.js';
export * from './oauth-par-response.js';
export * from './oauth-password-grant-token-request.js';
export * from './oauth-protected-resource-metadata.js';
export * from './oauth-refresh-token-grant-token-request.js';
export * from './oauth-refresh-token.js';
export * from './oauth-request-uri.js';
export * from './oauth-response-mode.js';
export * from './oauth-response-type.js';
export * from './oauth-scope.js';
export * from './oauth-token-identification.js';
export * from './oauth-token-request.js';
export * from './oauth-token-response.js';

@@ -24,0 +37,0 @@ export * from './oauth-token-type.js';

@@ -19,12 +19,17 @@ "use strict";

__exportStar(require("./util.js"), exports);
__exportStar(require("./access-token.js"), exports);
__exportStar(require("./atproto-loopback-client-metadata.js"), exports);
__exportStar(require("./oauth-client-id-discoverable.js"), exports);
__exportStar(require("./oauth-client-id-loopback.js"), exports);
__exportStar(require("./oauth-authentication-request-parameters.js"), exports);
__exportStar(require("./oauth-access-token.js"), exports);
__exportStar(require("./oauth-authorization-code-grant-token-request.js"), exports);
__exportStar(require("./oauth-authorization-details.js"), exports);
__exportStar(require("./oauth-authorization-request-jar.js"), exports);
__exportStar(require("./oauth-authorization-request-par.js"), exports);
__exportStar(require("./oauth-authorization-request-parameters.js"), exports);
__exportStar(require("./oauth-authorization-request-query.js"), exports);
__exportStar(require("./oauth-authorization-request-uri.js"), exports);
__exportStar(require("./oauth-authorization-server-metadata.js"), exports);
__exportStar(require("./oauth-client-credentials-grant-token-request.js"), exports);
__exportStar(require("./oauth-client-credentials.js"), exports);
__exportStar(require("./oauth-client-id-discoverable.js"), exports);
__exportStar(require("./oauth-client-id-loopback.js"), exports);
__exportStar(require("./oauth-client-id.js"), exports);
__exportStar(require("./oauth-client-identification.js"), exports);
__exportStar(require("./oauth-client-metadata.js"), exports);

@@ -34,7 +39,15 @@ __exportStar(require("./oauth-endpoint-auth-method.js"), exports);

__exportStar(require("./oauth-grant-type.js"), exports);
__exportStar(require("./oauth-introspection-response.js"), exports);
__exportStar(require("./oauth-issuer-identifier.js"), exports);
__exportStar(require("./oauth-par-response.js"), exports);
__exportStar(require("./oauth-password-grant-token-request.js"), exports);
__exportStar(require("./oauth-protected-resource-metadata.js"), exports);
__exportStar(require("./oauth-refresh-token-grant-token-request.js"), exports);
__exportStar(require("./oauth-refresh-token.js"), exports);
__exportStar(require("./oauth-request-uri.js"), exports);
__exportStar(require("./oauth-response-mode.js"), exports);
__exportStar(require("./oauth-response-type.js"), exports);
__exportStar(require("./oauth-scope.js"), exports);
__exportStar(require("./oauth-token-identification.js"), exports);
__exportStar(require("./oauth-token-request.js"), exports);
__exportStar(require("./oauth-token-response.js"), exports);

@@ -41,0 +54,0 @@ __exportStar(require("./oauth-token-type.js"), exports);

@@ -18,3 +18,3 @@ import { z } from 'zod';

grant_types_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodEnum<["S256", "plain"]>, "many">>;
ui_locales_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;

@@ -59,3 +59,3 @@ id_token_signing_alg_values_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -97,3 +97,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -134,3 +134,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodEnum<["S256", "plain"]>, "many">>;
ui_locales_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;

@@ -175,3 +175,3 @@ id_token_signing_alg_values_supported: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -213,3 +213,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -251,3 +251,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -289,3 +289,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -327,3 +327,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -365,3 +365,3 @@ id_token_signing_alg_values_supported?: string[] | undefined;

grant_types_supported?: string[] | undefined;
code_challenge_methods_supported?: string[] | undefined;
code_challenge_methods_supported?: ("S256" | "plain")[] | undefined;
ui_locales_supported?: string[] | undefined;

@@ -368,0 +368,0 @@ id_token_signing_alg_values_supported?: string[] | undefined;

@@ -5,2 +5,3 @@ "use strict";

const zod_1 = require("zod");
const oauth_code_challenge_method_js_1 = require("./oauth-code-challenge-method.js");
const oauth_issuer_identifier_js_1 = require("./oauth-issuer-identifier.js");

@@ -23,3 +24,6 @@ /**

grant_types_supported: zod_1.z.array(zod_1.z.string()).optional(),
code_challenge_methods_supported: zod_1.z.array(zod_1.z.string()).min(1).optional(),
code_challenge_methods_supported: zod_1.z
.array(oauth_code_challenge_method_js_1.oauthCodeChallengeMethodSchema)
.min(1)
.optional(),
ui_locales_supported: zod_1.z.array(zod_1.z.string()).optional(),

@@ -26,0 +30,0 @@ id_token_signing_alg_values_supported: zod_1.z.array(zod_1.z.string()).optional(),

@@ -12,3 +12,3 @@ import { z } from 'zod';

*
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
* @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
*/

@@ -25,2 +25,3 @@ client_assertion: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, `${string}.${string}.${string}`, string>;

}>;
export type OAuthClientCredentialsJwtBearer = z.infer<typeof oauthClientCredentialsJwtBearerSchema>;
export declare const oauthClientCredentialsSecretPostSchema: z.ZodObject<{

@@ -36,2 +37,11 @@ client_id: z.ZodString;

}>;
export type OAuthClientCredentialsSecretPost = z.infer<typeof oauthClientCredentialsSecretPostSchema>;
export declare const oauthClientCredentialsNoneSchema: z.ZodObject<{
client_id: z.ZodString;
}, "strip", z.ZodTypeAny, {
client_id: string;
}, {
client_id: string;
}>;
export type OAuthClientCredentialsNone = z.infer<typeof oauthClientCredentialsNoneSchema>;
export declare const oauthClientCredentialsSchema: z.ZodUnion<[z.ZodObject<{

@@ -47,3 +57,3 @@ client_id: z.ZodString;

*
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
* @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
*/

@@ -68,4 +78,10 @@ client_assertion: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, `${string}.${string}.${string}`, string>;

client_secret: string;
}>, z.ZodObject<{
client_id: z.ZodString;
}, "strip", z.ZodTypeAny, {
client_id: string;
}, {
client_id: string;
}>]>;
export type OAuthClientCredentials = z.infer<typeof oauthClientCredentialsSchema>;
//# sourceMappingURL=oauth-client-credentials.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.oauthClientCredentialsSchema = exports.oauthClientCredentialsSecretPostSchema = exports.oauthClientCredentialsJwtBearerSchema = void 0;
exports.oauthClientCredentialsSchema = exports.oauthClientCredentialsNoneSchema = exports.oauthClientCredentialsSecretPostSchema = exports.oauthClientCredentialsJwtBearerSchema = void 0;
const zod_1 = require("zod");

@@ -18,3 +18,3 @@ const jwk_1 = require("@atproto/jwk");

*
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
* @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
*/

@@ -27,6 +27,12 @@ client_assertion: jwk_1.signedJwtSchema,

});
exports.oauthClientCredentialsNoneSchema = zod_1.z.object({
client_id: oauth_client_id_js_1.oauthClientIdSchema,
});
//
exports.oauthClientCredentialsSchema = zod_1.z.union([
exports.oauthClientCredentialsJwtBearerSchema,
exports.oauthClientCredentialsSecretPostSchema,
// Must be last since it is less specific
exports.oauthClientCredentialsNoneSchema,
]);
//# sourceMappingURL=oauth-client-credentials.js.map

@@ -6,4 +6,5 @@ import { OAuthClientId } from './oauth-client-id.js';

export type OAuthClientIdDiscoverable = OAuthClientId & `https://${string}`;
export declare function isOAuthClientIdDiscoverable<C extends OAuthClientId>(clientId: C): clientId is C & OAuthClientIdDiscoverable;
export declare function parseOAuthDiscoverableClientId(clientId: OAuthClientId): URL;
export declare function isOAuthClientIdDiscoverable(clientId: string): clientId is OAuthClientIdDiscoverable;
export declare function assertOAuthDiscoverableClientId(value: string): asserts value is OAuthClientIdDiscoverable;
export declare function parseOAuthDiscoverableClientId(clientId: string): URL;
//# sourceMappingURL=oauth-client-id-discoverable.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseOAuthDiscoverableClientId = exports.isOAuthClientIdDiscoverable = void 0;
const oauth_client_id_url_js_1 = require("./oauth-client-id-url.js");
exports.parseOAuthDiscoverableClientId = exports.assertOAuthDiscoverableClientId = exports.isOAuthClientIdDiscoverable = void 0;
const util_js_1 = require("./util.js");

@@ -16,30 +15,34 @@ function isOAuthClientIdDiscoverable(clientId) {

exports.isOAuthClientIdDiscoverable = isOAuthClientIdDiscoverable;
function assertOAuthDiscoverableClientId(value) {
void parseOAuthDiscoverableClientId(value);
}
exports.assertOAuthDiscoverableClientId = assertOAuthDiscoverableClientId;
function parseOAuthDiscoverableClientId(clientId) {
const url = (0, oauth_client_id_url_js_1.parseOAuthClientIdUrl)(clientId);
// Optimization: cheap checks first
if (url.hostname === 'localhost') {
throw new TypeError('ClientID must not be a loopback hostname');
}
const url = new URL(clientId);
if (url.protocol !== 'https:') {
throw new TypeError('ClientID must use the "https:" protocol');
}
if (url.username || url.password) {
throw new TypeError('ClientID must not contain credentials');
}
if (url.hash) {
throw new TypeError('ClientID must not contain a fragment');
}
if (url.username || url.password) {
throw new TypeError('ClientID must not contain credentials');
if (url.hostname === 'localhost') {
throw new TypeError('ClientID hostname must not be "localhost"');
}
if (url.pathname === '/') {
throw new TypeError('ClientID must contain a path (e.g. "/client-metadata")');
throw new TypeError('ClientID must contain a path component (e.g. "/client-metadata.json")');
}
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
throw new TypeError('ClientID must not end with a trailing slash');
if (url.pathname.endsWith('/')) {
throw new TypeError('ClientID path must not end with a trailing slash');
}
if (url.pathname.includes('//')) {
throw new TypeError(`ClientID must not contain any double slashes in its path`);
if ((0, util_js_1.isHostnameIP)(url.hostname)) {
throw new TypeError('ClientID hostname must not be an IP address');
}
// Note: Query string is allowed
// Note: no restriction on the port for non-loopback URIs
if ((0, util_js_1.isIP)(url.hostname)) {
throw new TypeError('ClientID must not be an IP address');
// URL constructor normalizes the URL, so we extract the path manually to
// avoid normalization, then compare it to the normalized path to ensure
// that the URL does not contain path traversal or other unexpected characters
if ((0, util_js_1.extractUrlPath)(clientId) !== url.pathname) {
throw new TypeError(`ClientID must be in canonical form ("${url.href}", got "${clientId}")`);
}

@@ -46,0 +49,0 @@ return url;

import { OAuthClientId } from './oauth-client-id.js';
export type OAuthClientIdLoopback = OAuthClientId & `http://localhost${'' | `${'/' | '?' | '#'}${string}`}`;
export declare function isOAuthClientIdLoopback<C extends OAuthClientId>(clientId: C): clientId is C & OAuthClientIdLoopback;
export declare function parseOAuthLoopbackClientId(clientId: OAuthClientId): URL;
import { OAuthScope } from './oauth-scope.js';
declare const OAUTH_CLIENT_ID_LOOPBACK_URL = "http://localhost";
export type OAuthClientIdLoopback = OAuthClientId & `${typeof OAUTH_CLIENT_ID_LOOPBACK_URL}${'' | '/'}${'' | `?${string}`}`;
export declare function isOAuthClientIdLoopback(clientId: string): clientId is OAuthClientIdLoopback;
export declare function assertOAuthLoopbackClientId(clientId: string): asserts clientId is OAuthClientIdLoopback;
export declare function parseOAuthLoopbackClientId(clientId: string): {
scope?: OAuthScope;
redirect_uris?: [string, ...string[]];
};
export {};
//# sourceMappingURL=oauth-client-id-loopback.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseOAuthLoopbackClientId = exports.isOAuthClientIdLoopback = void 0;
const oauth_client_id_url_js_1 = require("./oauth-client-id-url.js");
exports.parseOAuthLoopbackClientId = exports.assertOAuthLoopbackClientId = exports.isOAuthClientIdLoopback = void 0;
const oauth_scope_js_1 = require("./oauth-scope.js");
const util_js_1 = require("./util.js");
const OAUTH_CLIENT_ID_LOOPBACK_URL = 'http://localhost';
function isOAuthClientIdLoopback(clientId) {

@@ -15,31 +17,66 @@ try {

exports.isOAuthClientIdLoopback = isOAuthClientIdLoopback;
function assertOAuthLoopbackClientId(clientId) {
void parseOAuthLoopbackClientId(clientId);
}
exports.assertOAuthLoopbackClientId = assertOAuthLoopbackClientId;
// @TODO: should we turn this into a zod schema? (more coherent error with other
// validation functions)
function parseOAuthLoopbackClientId(clientId) {
const url = (0, oauth_client_id_url_js_1.parseOAuthClientIdUrl)(clientId);
// Optimization: cheap checks first
if (url.protocol !== 'http:') {
throw new TypeError('Loopback ClientID must use the "http:" protocol');
if (!clientId.startsWith(OAUTH_CLIENT_ID_LOOPBACK_URL)) {
throw new TypeError(`Loopback ClientID must start with "${OAUTH_CLIENT_ID_LOOPBACK_URL}"`);
}
if (url.hostname !== 'localhost') {
throw new TypeError('Loopback ClientID must use the "localhost" hostname');
else if (clientId.includes('#', OAUTH_CLIENT_ID_LOOPBACK_URL.length)) {
throw new TypeError('Loopback ClientID must not contain a hash component');
}
if (url.hash) {
throw new TypeError('Loopback ClientID must not contain a fragment');
const queryStringIdx = clientId.length > OAUTH_CLIENT_ID_LOOPBACK_URL.length &&
clientId[OAUTH_CLIENT_ID_LOOPBACK_URL.length] === '/'
? OAUTH_CLIENT_ID_LOOPBACK_URL.length + 1
: OAUTH_CLIENT_ID_LOOPBACK_URL.length;
if (clientId.length === queryStringIdx) {
return {}; // no query string to parse
}
if (url.username || url.password) {
throw new TypeError('Loopback ClientID must not contain credentials');
if (clientId[queryStringIdx] !== '?') {
throw new TypeError('Loopback ClientID must not contain a path component');
}
if (url.port) {
throw new TypeError('Loopback ClientID must not contain a port');
const searchParams = new URLSearchParams(clientId.slice(queryStringIdx + 1));
for (const name of searchParams.keys()) {
if (name !== 'redirect_uri' && name !== 'scope') {
throw new TypeError(`Invalid query parameter "${name}" in client ID`);
}
}
// Note: url.pathname === '/' is allowed for loopback URIs
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
throw new TypeError('Loopback ClientID must not end with a trailing slash');
const scope = searchParams.get('scope') ?? undefined;
if (scope != null) {
if (searchParams.getAll('scope').length > 1) {
throw new TypeError('Loopback ClientID must contain at most one scope query parameter');
}
else if (!oauth_scope_js_1.oauthScopeSchema.safeParse(scope).success) {
throw new TypeError('Invalid scope query parameter in client ID');
}
}
if (url.pathname.includes('//')) {
throw new TypeError(`Loopback ClientID must not contain any double slashes in its path`);
const redirect_uris = searchParams.has('redirect_uri')
? searchParams.getAll('redirect_uri')
: undefined;
if (redirect_uris) {
for (const uri of redirect_uris) {
const url = (0, util_js_1.safeUrl)(uri);
if (!url) {
throw new TypeError(`Invalid redirect_uri in client ID: ${uri}`);
}
if (url.protocol !== 'http:') {
throw new TypeError(`Loopback ClientID must use "http:" redirect_uri's (got ${uri})`);
}
if (url.hostname === 'localhost') {
throw new TypeError(`Loopback ClientID must not use "localhost" as redirect_uri hostname (got ${uri})`);
}
if (!(0, util_js_1.isLoopbackHost)(url.hostname)) {
throw new TypeError(`Loopback ClientID must use loopback addresses as redirect_uri's (got ${uri})`);
}
}
}
// Note: Query string is allowed
return url;
return {
scope,
redirect_uris,
};
}
exports.parseOAuthLoopbackClientId = parseOAuthLoopbackClientId;
//# sourceMappingURL=oauth-client-id-loopback.js.map

@@ -10,2 +10,3 @@ "use strict";

const oauth_response_type_js_1 = require("./oauth-response-type.js");
const oauth_scope_js_1 = require("./oauth-scope.js");
// https://openid.net/specs/openid-connect-registration-1_0.html

@@ -27,3 +28,3 @@ // https://datatracker.ietf.org/doc/html/rfc7591

.default(['authorization_code']),
scope: zod_1.z.string().optional(),
scope: oauth_scope_js_1.oauthScopeSchema.optional(),
token_endpoint_auth_method: oauth_endpoint_auth_method_js_1.oauthEndpointAuthMethod

@@ -30,0 +31,0 @@ .default('none')

import { z } from 'zod';
export declare const oauthParResponseSchema: z.ZodObject<{
request_uri: z.ZodString;
expires_in: z.ZodNumber;
}, "strip", z.ZodTypeAny, {
request_uri: string;
expires_in: number;
}, {
request_uri: string;
expires_in: number;
}>;
export type OAuthParResponse = z.infer<typeof oauthParResponseSchema>;
//# sourceMappingURL=oauth-par-response.d.ts.map

@@ -7,3 +7,4 @@ "use strict";

request_uri: zod_1.z.string(),
expires_in: zod_1.z.number().int().positive(),
});
//# sourceMappingURL=oauth-par-response.js.map

@@ -1,2 +0,2 @@

export declare function isIP(hostname: string): boolean;
export declare function isHostnameIP(hostname: string): boolean;
export type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]';

@@ -6,2 +6,3 @@ export declare function isLoopbackHost(host: unknown): host is LoopbackHost;

export declare function safeUrl(input: URL | string): URL | null;
export declare function extractUrlPath(url: any): any;
//# sourceMappingURL=util.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeUrl = exports.isLoopbackUrl = exports.isLoopbackHost = exports.isIP = void 0;
function isIP(hostname) {
exports.extractUrlPath = exports.safeUrl = exports.isLoopbackUrl = exports.isLoopbackHost = exports.isHostnameIP = void 0;
function isHostnameIP(hostname) {
// IPv4

@@ -13,3 +13,3 @@ if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/))

}
exports.isIP = isIP;
exports.isHostnameIP = isHostnameIP;
function isLoopbackHost(host) {

@@ -33,2 +33,33 @@ return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';

exports.safeUrl = safeUrl;
function extractUrlPath(url) {
// Extracts the path from a URL, without relying on the URL constructor
// (because it normalizes the URL)
const endOfProtocol = url.startsWith('https://')
? 8
: url.startsWith('http://')
? 7
: -1;
if (endOfProtocol === -1) {
throw new TypeError('URL must use the "https:" or "http:" protocol');
}
const hashIdx = url.indexOf('#', endOfProtocol);
const questionIdx = url.indexOf('?', endOfProtocol);
const queryStrIdx = questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx)
? questionIdx
: -1;
const pathEnd = hashIdx === -1
? queryStrIdx === -1
? url.length
: queryStrIdx
: queryStrIdx === -1
? hashIdx
: Math.min(hashIdx, queryStrIdx);
const slashIdx = url.indexOf('/', endOfProtocol);
const pathStart = slashIdx === -1 || slashIdx > pathEnd ? pathEnd : slashIdx;
if (endOfProtocol === pathStart) {
throw new TypeError('URL must contain a host');
}
return url.substring(pathStart, pathEnd);
}
exports.extractUrlPath = extractUrlPath;
//# sourceMappingURL=util.js.map
{
"name": "@atproto/oauth-types",
"version": "0.1.4",
"version": "0.1.5",
"license": "MIT",

@@ -5,0 +5,0 @@ "description": "OAuth typing & validation library",

@@ -1,4 +0,3 @@

import { isOAuthClientIdLoopback } from './oauth-client-id-loopback.js'
import { parseOAuthLoopbackClientId } from './oauth-client-id-loopback.js'
import { OAuthClientMetadataInput } from './oauth-client-metadata.js'
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'

@@ -8,26 +7,14 @@ export function atprotoLoopbackClientMetadata(

): OAuthClientMetadataInput {
if (!isOAuthClientIdLoopback(clientId)) {
throw new TypeError(`Invalid loopback client ID ${clientId}`)
}
const {
scope = 'atproto',
redirect_uris = [`http://127.0.0.1/`, `http://[::1]/`],
} = parseOAuthLoopbackClientId(clientId)
const { origin, pathname, searchParams } = parseOAuthClientIdUrl(clientId)
for (const name of searchParams.keys()) {
if (name !== 'redirect_uri') {
throw new TypeError(`Invalid query parameter ${name} in client ID`)
}
}
const redirectUris = searchParams.getAll('redirect_uri')
return {
client_id: clientId,
scope,
redirect_uris,
client_name: 'Loopback client',
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
redirect_uris: (redirectUris.length
? redirectUris
: (['127.0.0.1', '[::1]'] as const).map(
(ip) =>
Object.assign(new URL(pathname, origin), { hostname: ip }).href,
)) as [string, ...string[]],
token_endpoint_auth_method: 'none',

@@ -34,0 +21,0 @@ application_type: 'native',

export * from './constants.js'
export * from './util.js'
export * from './access-token.js'
export * from './atproto-loopback-client-metadata.js'
export * from './oauth-client-id-discoverable.js'
export * from './oauth-client-id-loopback.js'
export * from './oauth-authentication-request-parameters.js'
export * from './oauth-access-token.js'
export * from './oauth-authorization-code-grant-token-request.js'
export * from './oauth-authorization-details.js'
export * from './oauth-authorization-request-jar.js'
export * from './oauth-authorization-request-par.js'
export * from './oauth-authorization-request-parameters.js'
export * from './oauth-authorization-request-query.js'
export * from './oauth-authorization-request-uri.js'
export * from './oauth-authorization-server-metadata.js'
export * from './oauth-client-credentials-grant-token-request.js'
export * from './oauth-client-credentials.js'
export * from './oauth-client-id-discoverable.js'
export * from './oauth-client-id-loopback.js'
export * from './oauth-client-id.js'
export * from './oauth-client-identification.js'
export * from './oauth-client-metadata.js'

@@ -18,7 +23,15 @@ export * from './oauth-endpoint-auth-method.js'

export * from './oauth-grant-type.js'
export * from './oauth-introspection-response.js'
export * from './oauth-issuer-identifier.js'
export * from './oauth-par-response.js'
export * from './oauth-password-grant-token-request.js'
export * from './oauth-protected-resource-metadata.js'
export * from './oauth-refresh-token-grant-token-request.js'
export * from './oauth-refresh-token.js'
export * from './oauth-request-uri.js'
export * from './oauth-response-mode.js'
export * from './oauth-response-type.js'
export * from './oauth-scope.js'
export * from './oauth-token-identification.js'
export * from './oauth-token-request.js'
export * from './oauth-token-response.js'

@@ -25,0 +38,0 @@ export * from './oauth-token-type.js'

import { z } from 'zod'
import { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'
import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'

@@ -22,3 +23,6 @@

grant_types_supported: z.array(z.string()).optional(),
code_challenge_methods_supported: z.array(z.string()).min(1).optional(),
code_challenge_methods_supported: z
.array(oauthCodeChallengeMethodSchema)
.min(1)
.optional(),
ui_locales_supported: z.array(z.string()).optional(),

@@ -25,0 +29,0 @@ id_token_signing_alg_values_supported: z.array(z.string()).optional(),

@@ -17,3 +17,3 @@ import { z } from 'zod'

*
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
* @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
*/

@@ -23,2 +23,6 @@ client_assertion: signedJwtSchema,

export type OAuthClientCredentialsJwtBearer = z.infer<
typeof oauthClientCredentialsJwtBearerSchema
>
export const oauthClientCredentialsSecretPostSchema = z.object({

@@ -29,5 +33,21 @@ client_id: oauthClientIdSchema,

export type OAuthClientCredentialsSecretPost = z.infer<
typeof oauthClientCredentialsSecretPostSchema
>
export const oauthClientCredentialsNoneSchema = z.object({
client_id: oauthClientIdSchema,
})
export type OAuthClientCredentialsNone = z.infer<
typeof oauthClientCredentialsNoneSchema
>
//
export const oauthClientCredentialsSchema = z.union([
oauthClientCredentialsJwtBearerSchema,
oauthClientCredentialsSecretPostSchema,
// Must be last since it is less specific
oauthClientCredentialsNoneSchema,
])

@@ -34,0 +54,0 @@

@@ -1,4 +0,3 @@

import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
import { OAuthClientId } from './oauth-client-id.js'
import { isIP } from './util.js'
import { extractUrlPath, isHostnameIP } from './util.js'

@@ -10,5 +9,5 @@ /**

export function isOAuthClientIdDiscoverable<C extends OAuthClientId>(
clientId: C,
): clientId is C & OAuthClientIdDiscoverable {
export function isOAuthClientIdDiscoverable(
clientId: string,
): clientId is OAuthClientIdDiscoverable {
try {

@@ -22,11 +21,11 @@ parseOAuthDiscoverableClientId(clientId)

export function parseOAuthDiscoverableClientId(clientId: OAuthClientId): URL {
const url = parseOAuthClientIdUrl(clientId)
export function assertOAuthDiscoverableClientId(
value: string,
): asserts value is OAuthClientIdDiscoverable {
void parseOAuthDiscoverableClientId(value)
}
// Optimization: cheap checks first
export function parseOAuthDiscoverableClientId(clientId: string): URL {
const url = new URL(clientId)
if (url.hostname === 'localhost') {
throw new TypeError('ClientID must not be a loopback hostname')
}
if (url.protocol !== 'https:') {

@@ -36,2 +35,6 @@ throw new TypeError('ClientID must use the "https:" protocol')

if (url.username || url.password) {
throw new TypeError('ClientID must not contain credentials')
}
if (url.hash) {

@@ -41,4 +44,4 @@ throw new TypeError('ClientID must not contain a fragment')

if (url.username || url.password) {
throw new TypeError('ClientID must not contain credentials')
if (url.hostname === 'localhost') {
throw new TypeError('ClientID hostname must not be "localhost"')
}

@@ -48,24 +51,24 @@

throw new TypeError(
'ClientID must contain a path (e.g. "/client-metadata")',
'ClientID must contain a path component (e.g. "/client-metadata.json")',
)
}
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
throw new TypeError('ClientID must not end with a trailing slash')
if (url.pathname.endsWith('/')) {
throw new TypeError('ClientID path must not end with a trailing slash')
}
if (url.pathname.includes('//')) {
if (isHostnameIP(url.hostname)) {
throw new TypeError('ClientID hostname must not be an IP address')
}
// URL constructor normalizes the URL, so we extract the path manually to
// avoid normalization, then compare it to the normalized path to ensure
// that the URL does not contain path traversal or other unexpected characters
if (extractUrlPath(clientId) !== url.pathname) {
throw new TypeError(
`ClientID must not contain any double slashes in its path`,
`ClientID must be in canonical form ("${url.href}", got "${clientId}")`,
)
}
// Note: Query string is allowed
// Note: no restriction on the port for non-loopback URIs
if (isIP(url.hostname)) {
throw new TypeError('ClientID must not be an IP address')
}
return url
}

@@ -1,10 +0,13 @@

import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
import { OAuthClientId } from './oauth-client-id.js'
import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
import { isLoopbackHost, safeUrl } from './util.js'
const OAUTH_CLIENT_ID_LOOPBACK_URL = 'http://localhost'
export type OAuthClientIdLoopback = OAuthClientId &
`http://localhost${'' | `${'/' | '?' | '#'}${string}`}`
`${typeof OAUTH_CLIENT_ID_LOOPBACK_URL}${'' | '/'}${'' | `?${string}`}`
export function isOAuthClientIdLoopback<C extends OAuthClientId>(
clientId: C,
): clientId is C & OAuthClientIdLoopback {
export function isOAuthClientIdLoopback(
clientId: string,
): clientId is OAuthClientIdLoopback {
try {

@@ -18,42 +21,87 @@ parseOAuthLoopbackClientId(clientId)

export function parseOAuthLoopbackClientId(clientId: OAuthClientId): URL {
const url = parseOAuthClientIdUrl(clientId)
export function assertOAuthLoopbackClientId(
clientId: string,
): asserts clientId is OAuthClientIdLoopback {
void parseOAuthLoopbackClientId(clientId)
}
// Optimization: cheap checks first
if (url.protocol !== 'http:') {
throw new TypeError('Loopback ClientID must use the "http:" protocol')
// @TODO: should we turn this into a zod schema? (more coherent error with other
// validation functions)
export function parseOAuthLoopbackClientId(clientId: string): {
scope?: OAuthScope
redirect_uris?: [string, ...string[]]
} {
if (!clientId.startsWith(OAUTH_CLIENT_ID_LOOPBACK_URL)) {
throw new TypeError(
`Loopback ClientID must start with "${OAUTH_CLIENT_ID_LOOPBACK_URL}"`,
)
} else if (clientId.includes('#', OAUTH_CLIENT_ID_LOOPBACK_URL.length)) {
throw new TypeError('Loopback ClientID must not contain a hash component')
}
if (url.hostname !== 'localhost') {
throw new TypeError('Loopback ClientID must use the "localhost" hostname')
const queryStringIdx =
clientId.length > OAUTH_CLIENT_ID_LOOPBACK_URL.length &&
clientId[OAUTH_CLIENT_ID_LOOPBACK_URL.length] === '/'
? OAUTH_CLIENT_ID_LOOPBACK_URL.length + 1
: OAUTH_CLIENT_ID_LOOPBACK_URL.length
if (clientId.length === queryStringIdx) {
return {} // no query string to parse
}
if (url.hash) {
throw new TypeError('Loopback ClientID must not contain a fragment')
if (clientId[queryStringIdx] !== '?') {
throw new TypeError('Loopback ClientID must not contain a path component')
}
if (url.username || url.password) {
throw new TypeError('Loopback ClientID must not contain credentials')
const searchParams = new URLSearchParams(clientId.slice(queryStringIdx + 1))
for (const name of searchParams.keys()) {
if (name !== 'redirect_uri' && name !== 'scope') {
throw new TypeError(`Invalid query parameter "${name}" in client ID`)
}
}
if (url.port) {
throw new TypeError('Loopback ClientID must not contain a port')
const scope = searchParams.get('scope') ?? undefined
if (scope != null) {
if (searchParams.getAll('scope').length > 1) {
throw new TypeError(
'Loopback ClientID must contain at most one scope query parameter',
)
} else if (!oauthScopeSchema.safeParse(scope).success) {
throw new TypeError('Invalid scope query parameter in client ID')
}
}
// Note: url.pathname === '/' is allowed for loopback URIs
const redirect_uris = searchParams.has('redirect_uri')
? (searchParams.getAll('redirect_uri') as [string, ...string[]])
: undefined
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
throw new TypeError('Loopback ClientID must not end with a trailing slash')
if (redirect_uris) {
for (const uri of redirect_uris) {
const url = safeUrl(uri)
if (!url) {
throw new TypeError(`Invalid redirect_uri in client ID: ${uri}`)
}
if (url.protocol !== 'http:') {
throw new TypeError(
`Loopback ClientID must use "http:" redirect_uri's (got ${uri})`,
)
}
if (url.hostname === 'localhost') {
throw new TypeError(
`Loopback ClientID must not use "localhost" as redirect_uri hostname (got ${uri})`,
)
}
if (!isLoopbackHost(url.hostname)) {
throw new TypeError(
`Loopback ClientID must use loopback addresses as redirect_uri's (got ${uri})`,
)
}
}
}
if (url.pathname.includes('//')) {
throw new TypeError(
`Loopback ClientID must not contain any double slashes in its path`,
)
return {
scope,
redirect_uris,
}
// Note: Query string is allowed
return url
}

@@ -8,2 +8,3 @@ import { jwksPubSchema } from '@atproto/jwk'

import { oauthResponseTypeSchema } from './oauth-response-type.js'
import { oauthScopeSchema } from './oauth-scope.js'

@@ -26,3 +27,3 @@ // https://openid.net/specs/openid-connect-registration-1_0.html

.default(['authorization_code']),
scope: z.string().optional(),
scope: oauthScopeSchema.optional(),
token_endpoint_auth_method: oauthEndpointAuthMethod

@@ -29,0 +30,0 @@ .default('none')

@@ -5,4 +5,5 @@ import { z } from 'zod'

request_uri: z.string(),
expires_in: z.number().int().positive(),
})
export type OAuthParResponse = z.infer<typeof oauthParResponseSchema>

@@ -1,2 +0,2 @@

export function isIP(hostname: string) {
export function isHostnameIP(hostname: string) {
// IPv4

@@ -29,1 +29,41 @@ if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) return true

}
export function extractUrlPath(url) {
// Extracts the path from a URL, without relying on the URL constructor
// (because it normalizes the URL)
const endOfProtocol = url.startsWith('https://')
? 8
: url.startsWith('http://')
? 7
: -1
if (endOfProtocol === -1) {
throw new TypeError('URL must use the "https:" or "http:" protocol')
}
const hashIdx = url.indexOf('#', endOfProtocol)
const questionIdx = url.indexOf('?', endOfProtocol)
const queryStrIdx =
questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx)
? questionIdx
: -1
const pathEnd =
hashIdx === -1
? queryStrIdx === -1
? url.length
: queryStrIdx
: queryStrIdx === -1
? hashIdx
: Math.min(hashIdx, queryStrIdx)
const slashIdx = url.indexOf('/', endOfProtocol)
const pathStart = slashIdx === -1 || slashIdx > pathEnd ? pathEnd : slashIdx
if (endOfProtocol === pathStart) {
throw new TypeError('URL must contain a host')
}
return url.substring(pathStart, pathEnd)
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc