init
This commit is contained in:
193
node_modules/matrix-js-sdk/lib/oauth/authorize.js
generated
vendored
Normal file
193
node_modules/matrix-js-sdk/lib/oauth/authorize.js
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
||||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { secureRandomString } from "../randomstring.js";
|
||||
import { OAuth2Error } from "./error.js";
|
||||
import { hasOptionalNumberProperty, hasOptionalStringProperty, hasRequiredNumberProperty, hasRequiredStringProperty, isRecord } from "../@types/type-guards.js";
|
||||
import { Method } from "../http-api/index.js";
|
||||
import { OAuthGrantType } from "./register.js";
|
||||
import { sleep } from "../utils.js";
|
||||
|
||||
/**
|
||||
* The expected response type from the token endpoint during authorization code flow
|
||||
* Normalized to always use capitalized 'Bearer' for token_type
|
||||
*
|
||||
* See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.4
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metadata from OAuth 2.0 token_endpoint as per
|
||||
* https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
|
||||
* With validated properties required in type
|
||||
*
|
||||
* This response is expected for the authorization code grant and refresh token grant,
|
||||
* as defined in the Matrix spec.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate the given response matches the format expected for a {@link ValidTokenResponse}
|
||||
* @param response - the response to validate
|
||||
* @throws if the response does not match the expected format
|
||||
*/
|
||||
export function validateBearerTokenResponse(response) {
|
||||
if (!isRecord(response) || !hasRequiredStringProperty(response, "token_type") ||
|
||||
// token_type is case-insensitive, some OPs return `token_type: "bearer"`
|
||||
response["token_type"].toLowerCase() !== "bearer" || !hasRequiredStringProperty(response, "access_token") || !hasOptionalNumberProperty(response, "expires_in") || !hasOptionalStringProperty(response, "refresh_token") || !hasOptionalStringProperty(response, "scope")) {
|
||||
throw new Error(OAuth2Error.InvalidBearerTokenResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the scope used in authorization request with OAuth2 IdP
|
||||
* @returns scope
|
||||
*/
|
||||
export const generateScope = deviceId => {
|
||||
const safeDeviceId = deviceId ?? secureRandomString(10);
|
||||
return `urn:matrix:client:api:* urn:matrix:client:device:${safeDeviceId}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize token_type to use capital case to make consuming the token response easier
|
||||
* token_type is case insensitive, and it is spec-compliant for OPs to return token_type: "bearer"
|
||||
* Later, when used in auth headers it is case sensitive and must be Bearer
|
||||
* See: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.4
|
||||
*
|
||||
* @param response - validated token response
|
||||
* @returns response with token_type set to 'Bearer'
|
||||
*/
|
||||
export const normalizeBearerTokenResponseTokenType = response => _objectSpread(_objectSpread({}, response), {}, {
|
||||
token_type: "Bearer"
|
||||
});
|
||||
|
||||
/**
|
||||
* Response from the OAuth2 token endpoint when exchanging a token for grant_type device_code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate the given response matches the format expected for a {@link DeviceAccessTokenResponse}
|
||||
* @param response - the response to validate
|
||||
* @throws if the response does not match the expected format
|
||||
*/
|
||||
export function isValidDeviceAccessTokenResponse(response) {
|
||||
return isRecord(response) && hasRequiredStringProperty(response, "access_token") && hasRequiredStringProperty(response, "token_type") && hasOptionalStringProperty(response, "refresh_token") && hasOptionalStringProperty(response, "scope") && hasOptionalNumberProperty(response, "expires_in");
|
||||
}
|
||||
|
||||
/**
|
||||
* Error from the OAuth2 token endpoint when exchanging a token for grant_type device_code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Response from the OAuth2 device authorization endpoint.
|
||||
* As specified in https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate the given response matches the format expected for a {@link DeviceAuthorizationResponse}
|
||||
* @param response - the response to validate
|
||||
* @throws if the response does not match the expected format
|
||||
*/
|
||||
export function validateDeviceAuthorizationResponse(response) {
|
||||
if (!isRecord(response) || !hasRequiredStringProperty(response, "device_code") || !hasRequiredStringProperty(response, "user_code") || !hasRequiredStringProperty(response, "verification_uri") || !hasRequiredNumberProperty(response, "expires_in") || !hasOptionalStringProperty(response, "verification_uri_complete") || !hasOptionalNumberProperty(response, "interval")) {
|
||||
throw new Error(OAuth2Error.InvalidDeviceAuthorizationResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin OAuth2 device authorization flow.
|
||||
* @param options - The device authorization parameters.
|
||||
* @param options.clientId - the client ID returned from client registration.
|
||||
* @param options.scope - the scope to request for authorization.
|
||||
* @param options.metadata - the validated OAuth2 metadata for the Identity Provider.
|
||||
* @returns a promise that resolves to a device access token response,
|
||||
* or an error response if the user denies authorization or the device code expires.
|
||||
*/
|
||||
export const startDeviceAuthorization = async ({
|
||||
clientId,
|
||||
scope,
|
||||
metadata
|
||||
}) => {
|
||||
const body = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
scope: scope
|
||||
}).toString();
|
||||
const url = metadata.device_authorization_endpoint;
|
||||
if (!url) {
|
||||
throw new Error("No device_authorization_endpoint given");
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: Method.Post,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body
|
||||
});
|
||||
const data = await response.json();
|
||||
validateDeviceAuthorizationResponse(data);
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Polls the OAuth2 token endpoint until we get a device access token response, or encounter an unrecoverable error.
|
||||
* @param options - The device authorization parameters.
|
||||
* @param options.session - The session returned from a previous call to {@link startDeviceAuthorization}.
|
||||
* @param options.metadata - The validated OAuth2 metadata for the Identity Provider.
|
||||
* @param options.clientId - The client ID returned from client registration.
|
||||
* @returns a promise that resolves to a device access token response,
|
||||
* or an error response if the user denies authorization or the device code expires.
|
||||
*/
|
||||
export const waitForDeviceAuthorization = async ({
|
||||
session,
|
||||
metadata,
|
||||
clientId
|
||||
}) => {
|
||||
let interval = (session.interval ?? 5) * 1000; // poll interval
|
||||
const expiration = Date.now() + session.expires_in * 1000;
|
||||
do {
|
||||
const body = new URLSearchParams({
|
||||
device_code: session.device_code,
|
||||
grant_type: OAuthGrantType.DeviceAuthorization,
|
||||
client_id: clientId
|
||||
}).toString();
|
||||
const response = await fetch(metadata.token_endpoint, {
|
||||
method: Method.Post,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && isValidDeviceAccessTokenResponse(data)) {
|
||||
return data;
|
||||
}
|
||||
const errorResponse = data;
|
||||
switch (errorResponse.error) {
|
||||
case "authorization_pending":
|
||||
break;
|
||||
case "slow_down":
|
||||
interval += 5000;
|
||||
break;
|
||||
case "access_denied":
|
||||
case "expired_token":
|
||||
return errorResponse;
|
||||
}
|
||||
await sleep(interval);
|
||||
} while (Date.now() < expiration);
|
||||
return {
|
||||
error: "expired"
|
||||
};
|
||||
};
|
||||
//# sourceMappingURL=authorize.js.map
|
||||
Reference in New Issue
Block a user