splitwise
Version:
A TypeScript SDK for the Splitwise API.
67 lines • 2.76 kB
JavaScript
;
/**
* OAuth 2.0 Authorization Code grant with PKCE for Splitwise.
*
* Two-step flow:
* 1. `createAuthorizationUrl` builds the consent URL and generates the
* `state` and `code_verifier` the caller must persist.
* 2. After the user approves and Splitwise redirects back with `?code=...`,
* `exchangeAuthorizationCode` swaps that code for an access token.
*
* PKCE (RFC 7636) is mandatory here. The verifier never leaves the caller
* until step 2, which protects against authorization code interception.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAuthorizationUrl = createAuthorizationUrl;
exports.exchangeAuthorizationCode = exchangeAuthorizationCode;
const internal_js_1 = require("./internal.js");
/**
* URL-safe base64 per RFC 4648 §5: replace +/= with -_ and strip padding.
*/
function base64urlEncode(bytes) {
const binary = String.fromCharCode(...bytes);
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function randomBase64Url(byteLength) {
const bytes = new Uint8Array(byteLength);
crypto.getRandomValues(bytes);
return base64urlEncode(bytes);
}
async function sha256Base64Url(input) {
const data = new TextEncoder().encode(input);
const digest = await crypto.subtle.digest('SHA-256', data);
return base64urlEncode(new Uint8Array(digest));
}
async function createAuthorizationUrl(params, options = {}) {
const authorizeUrl = options.authorizeUrl ?? internal_js_1.DEFAULT_AUTHORIZE_URL;
// 32 bytes -> 43-char base64url string, comfortably inside RFC 7636's 43-128.
const state = params.state ?? randomBase64Url(32);
const codeVerifier = randomBase64Url(32);
const codeChallenge = await sha256Base64Url(codeVerifier);
const url = new URL(authorizeUrl);
url.searchParams.set('response_type', 'code');
url.searchParams.set('client_id', params.clientId);
url.searchParams.set('redirect_uri', params.redirectUri);
url.searchParams.set('state', state);
url.searchParams.set('code_challenge', codeChallenge);
url.searchParams.set('code_challenge_method', 'S256');
if (params.scope !== undefined) {
url.searchParams.set('scope', params.scope);
}
return {
url: url.toString(),
state,
codeVerifier,
};
}
async function exchangeAuthorizationCode(params, options = {}) {
return (0, internal_js_1.postTokenRequest)({
grant_type: 'authorization_code',
code: params.code,
redirect_uri: params.redirectUri,
client_id: params.clientId,
client_secret: params.clientSecret,
code_verifier: params.codeVerifier,
}, options);
}
//# sourceMappingURL=authorization-code.js.map