@donation-alerts/auth
Version:
Authentication provider for Donation Alerts API with ability to automatically refresh user tokens.
150 lines (149 loc) • 5.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAccessToken = getAccessToken;
exports.refreshAccessToken = refreshAccessToken;
exports.compareScopes = compareScopes;
const api_call_1 = require("@donation-alerts/api-call");
const common_1 = require("@donation-alerts/common");
const errors_1 = require("./errors");
function createAccessTokenFromData(data) {
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
obtainmentTimestamp: Date.now(),
};
}
/**
* Obtains an access token using client credentials and an authorization code.
*
* @remarks
* When a user authenticates your application, an authorization code is provided.
* This function exchanges that code for an access token, which can be used to make
* authorized requests to the Donation Alerts API.
*
* @param clientId The client ID of your application.
* @param clientSecret The client secret of your application.
* @param redirectUri The redirect URI specified in your application.
* @param code The authorization code returned after successful user authorization.
*
* @returns A promise resolving to an {@link AccessToken} object.
*
* @throws {@link HttpError} If an error occurs during the request, such as invalid credentials.
*
* @example
* ```ts
* try {
* const accessToken = await getAccessToken(
* 'your-client-id',
* 'your-client-secret',
* 'http://your-redirect-uri',
* 'authorization-code'
* );
*
* console.log('Access Token:', accessToken.accessToken);
* } catch (e) {
* console.error('Failed to get access token:', e);
* }
* ```
*/
async function getAccessToken(clientId, clientSecret, redirectUri, code) {
return createAccessTokenFromData(await (0, api_call_1.callDonationAlertsApi)({
type: 'auth',
url: 'token',
method: 'GET',
formBody: {
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code,
},
auth: false,
}));
}
/**
* Refreshes an expired access token using the refresh token.
*
* @remarks
* Access tokens eventually expire, but a refresh token can be used to obtain
* a new one without requiring user reauthorization. This function performs the
* necessary call to refresh the token and return a new {@link AccessToken} object.
*
* @param clientId The client ID of your application.
* @param clientSecret The client secret of your application.
* @param refreshToken The refresh token obtained during the initial authorization process.
* @param scopes Optional. The scopes to request for the new access token. Defaults to the original scopes.
*
* @returns A promise resolving to a new {@link AccessToken} object.
*
* @throws {@link HttpError} If an error occurs during the request, such as an invalid refresh token.
*
* @example
* ```ts
* try {
* const token = await refreshAccessToken(
* 'your-client-id',
* 'your-client-secret',
* 'refresh-token-here',
* ['oauth-user-show', 'oauth-donation-index']
* );
*
* console.log('New Access Token:', token.accessToken);
* } catch (e) {
* console.error('Failed to refresh access token:', e);
* }
* ```
*/
async function refreshAccessToken(clientId, clientSecret, refreshToken, scopes = []) {
return createAccessTokenFromData(await (0, api_call_1.callDonationAlertsApi)({
type: 'auth',
url: 'token',
method: 'POST',
formBody: {
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret,
scope: scopes,
},
auth: false,
}));
}
/**
* c.
*
* @remarks
* This function checks if the provided token has all the necessary scopes for an operation.
* If the token does not contain any of the requested scopes, a {@link MissingScopeError} is thrown
* for the caller to handle.
*
* @param scopesToCompare The list of scopes present in the token.
* @param requestedScopes The scopes needed for the operation. Default to an empty array.
* @param user The user ID associated with the token. This is used in the error message if scopes are missing.
*
* @throws {@link MissingScopeError} If the token does not include the required scopes.
*
* @example
* ```ts
* try {
* compareScopes(['oauth-user-show', 'oauth-donation-index'], ['oauth-custom_alert-store'], 12345);
* } catch (error) {
* if (error instanceof MissingScopeError) {
* console.error('Missing scopes:', error.missingScopes);
* }
* }
* ```
*/
function compareScopes(scopesToCompare, requestedScopes = [], user) {
const scopes = new Set(scopesToCompare);
const missingScopes = [];
for (const scope of requestedScopes) {
if (!scopes.has(scope)) {
missingScopes.push(scope);
}
}
if (missingScopes.length > 0) {
throw new errors_1.MissingScopeError(user ? (0, common_1.extractUserId)(user) : null, missingScopes, `The token does not have the requested scopes: ${requestedScopes.join(', ')}`);
}
}