@bc-koenro/oauth2-client
Version:
OAuth2 client for browsers and Node.js. Tiny footprint, PKCE support
147 lines • 5.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCodeChallenge = exports.generateCodeVerifier = exports.OAuth2AuthorizationCodeClient = void 0;
const client_1 = require("../client");
const error_1 = require("../error");
class OAuth2AuthorizationCodeClient {
constructor(client) {
this.client = client;
}
/**
* Returns the URi that the user should open in a browser to initiate the
* authorization_code flow.
*/
async getAuthorizeUri(params) {
const [codeChallenge, authorizationEndpoint] = await Promise.all([
params.codeVerifier ? getCodeChallenge(params.codeVerifier) : undefined,
this.client.getEndpoint('authorizationEndpoint')
]);
const query = {
client_id: this.client.settings.clientId,
response_type: 'code',
redirect_uri: params.redirectUri,
code_challenge_method: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[0],
code_challenge: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[1],
};
if (params.state) {
query.state = params.state;
}
if (params.scope) {
query.scope = params.scope.join(' ');
}
return authorizationEndpoint + '?' + (0, client_1.generateQueryString)(query);
}
async getTokenFromCodeRedirect(url, params) {
const { code } = await this.validateResponse(url, {
state: params.state
});
return this.getToken({
code,
redirectUri: params.redirectUri,
codeVerifier: params.codeVerifier,
});
}
/**
* After the user redirected back from the authorization endpoint, the
* url will contain a 'code' and other information.
*
* This function takes the url and validate the response. If the user
* redirected back with an error, an error will be thrown.
*/
async validateResponse(url, params) {
var _a;
const queryParams = new URL(url).searchParams;
if (queryParams.has('error')) {
throw new error_1.OAuth2Error((_a = queryParams.get('error_description')) !== null && _a !== void 0 ? _a : 'OAuth2 error', queryParams.get('error'), 0);
}
if (!queryParams.has('code'))
throw new Error(`The url did not contain a code parameter ${url}`);
if (params.state && params.state !== queryParams.get('state')) {
throw new Error(`The "state" parameter in the url did not match the expected value of ${params.state}`);
}
return {
code: queryParams.get('code'),
scope: queryParams.has('scope') ? queryParams.get('scope').split(' ') : undefined,
};
}
/**
* Receives an OAuth2 token using 'authorization_code' grant
*/
async getToken(params) {
const body = {
grant_type: 'authorization_code',
code: params.code,
redirect_uri: params.redirectUri,
code_verifier: params.codeVerifier,
};
return (0, client_1.tokenResponseToOAuth2Token)(this.client.request('tokenEndpoint', body));
}
}
exports.OAuth2AuthorizationCodeClient = OAuth2AuthorizationCodeClient;
async function generateCodeVerifier() {
const webCrypto = getWebCrypto();
if (webCrypto) {
const arr = new Uint8Array(32);
webCrypto.getRandomValues(arr);
return base64Url(arr);
}
else {
// Old node doesn't have 'webcrypto', so this is a fallback
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nodeCrypto = require('crypto');
return new Promise((res, rej) => {
nodeCrypto.randomBytes(32, (err, buf) => {
if (err)
rej(err);
res(buf.toString('base64url'));
});
});
}
}
exports.generateCodeVerifier = generateCodeVerifier;
async function getCodeChallenge(codeVerifier) {
const webCrypto = getWebCrypto();
if (webCrypto === null || webCrypto === void 0 ? void 0 : webCrypto.subtle) {
return ['S256', base64Url(await webCrypto.subtle.digest('SHA-256', stringToBuffer(codeVerifier)))];
}
else {
// Node 14.x fallback
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nodeCrypto = require('crypto');
const hash = nodeCrypto.createHash('sha256');
hash.update(stringToBuffer(codeVerifier));
return ['S256', hash.digest('base64url')];
}
}
exports.getCodeChallenge = getCodeChallenge;
function getWebCrypto() {
// Browsers
if ((typeof window !== 'undefined' && window.crypto)) {
return window.crypto;
}
// Web workers possibly
if ((typeof self !== 'undefined' && self.crypto)) {
return self.crypto;
}
// Node
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require('crypto');
if (crypto.webcrypto) {
return crypto.webcrypto;
}
return null;
}
function stringToBuffer(input) {
const buf = new Uint8Array(input.length);
for (let i = 0; i < input.length; i++) {
buf[i] = input.charCodeAt(i) & 0xFF;
}
return buf;
}
function base64Url(buf) {
return (btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, ''));
}
//# sourceMappingURL=authorization-code.js.map