UNPKG

integreat-authenticator-oauth2

Version:
152 lines 5.19 kB
import debugFn from 'debug'; import formTransformer from 'integreat-adapter-form/transformer.js'; import signJwt from './signJwt.js'; import { isObject } from './utils/is.js'; const VALID_GRANT_TYPES = [ 'authorizationCode', 'refreshToken', 'clientCredentials', 'jwtAssertion', ]; const debug = debugFn('integreat:authenticator:oauth2'); const serializeForm = (data) => formTransformer({})({})(data, { rev: true, context: [], value: data }); const base64Auth = (key, secret) => Buffer.from(`${key}:${secret}`).toString('base64'); const isValidOptions = (options) => !!options && typeof options.grantType === 'string' && VALID_GRANT_TYPES.includes(options.grantType); const baseFields = ['grantType', 'uri', 'key', 'secret']; const conditionalFields = { authorizationCode: ['redirectUri', 'code'], refreshToken: ['redirectUri', 'refreshToken'], }; function hasRequiredOptions(options) { const requiredFields = [ ...baseFields, ...(conditionalFields[options.grantType] || []), ]; return requiredFields.filter((field) => !options[field]); } async function parseData(response) { try { return (await response.json()); } catch { return null; } } function resolveTypeAndToken(options, authentication) { if (options.grantType === 'authorizationCode' && !authentication?.refreshToken) { return { grant_type: 'authorization_code', client_id: options.key, client_secret: options.secret, redirect_uri: options.redirectUri, code: options.code, }; } else { return { grant_type: 'refresh_token', client_id: options.key, client_secret: options.secret, redirect_uri: options.redirectUri, refresh_token: options.grantType === 'refreshToken' ? options.refreshToken : authentication?.refreshToken, }; } } function prepareFormData(options, authentication) { switch (options.grantType) { case 'authorizationCode': case 'refreshToken': return resolveTypeAndToken(options, authentication); case 'jwtAssertion': return { grantType: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: signJwt(options), }; default: return { grant_type: 'client_credentials', }; } } function getHeaders(options) { const headers = isObject(options.headers) ? Object.fromEntries(Object.entries(options.headers).filter((entry) => typeof entry[0] === 'string' || Array.isArray(entry[0]))) : {}; headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; if (options.grantType === 'clientCredentials') { headers.Authorization = `Basic ${base64Auth(options.key, options.secret)}`; } return headers; } const generateAuthentication = (data, options) => ({ status: 'granted', token: data.access_token, expire: Date.now() + data.expires_in * 1000, type: options.authHeaderType, ...(data.refresh_token ? { refreshToken: data.refresh_token } : {}), }); async function requestAuth(options, authentication) { const uri = options.uri; const body = serializeForm({ ...prepareFormData(options, authentication), ...(options.scope ? { scope: options.scope } : {}), }); const request = { method: 'POST', body, headers: getHeaders(options), }; debug(`Sending auth request: ${JSON.stringify(request)}`); let response; try { response = await fetch(uri, request); } catch (error) { const message = error instanceof Error ? error.message : String(error); debug(`Auth attempt failed: ${message}`); return { status: 'error', error: message }; } const data = await parseData(response); debug(`Received auth response: ${JSON.stringify(data)}`); if (response.ok) { if (data) { return generateAuthentication(data, options); } else { return { status: 'error', error: 'Invalid json response' }; } } else if (response.status === 400) { return { status: 'refused', error: data?.error_description ? `Refused by service: ${data?.error_description}` : 'Refused by service', }; } else { return { status: 'error', error: response.statusText }; } } export default async function authenticate(options, _action, _dispatch, authentication) { if (!isValidOptions(options)) { return { status: 'error', error: 'Unknown or missing grant type option', }; } const missingFields = hasRequiredOptions(options); if (missingFields.length > 0) { return { status: 'error', error: `Missing ${missingFields.join(', ')} option${missingFields.length > 1 ? 's' : ''}`, }; } return await requestAuth(options, authentication); } //# sourceMappingURL=authenticate.js.map