Files
pulse/lib/api/oauth.ts

80 lines
2.9 KiB
TypeScript

import { AUTH_URL, APP_URL } from './client'
function generateRandomString(length: number): string {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
let result = ''
const values = new Uint8Array(length)
crypto.getRandomValues(values)
for (let i = 0; i < length; i++) {
result += charset[values[i] % charset.length]
}
return result
}
async function generateCodeChallenge(codeVerifier: string): Promise<string> {
const encoder = new TextEncoder()
const data = encoder.encode(codeVerifier)
const digest = await crypto.subtle.digest('SHA-256', data)
// Convert ArrayBuffer to Base64URL string
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '')
}
export interface OAuthParams {
state: string
codeVerifier: string
codeChallenge: string
}
export async function generateOAuthParams(): Promise<OAuthParams> {
const state = generateRandomString(32)
const codeVerifier = generateRandomString(64)
const codeChallenge = await generateCodeChallenge(codeVerifier)
return {
state,
codeVerifier,
codeChallenge
}
}
export async function initiateOAuthFlow(redirectPath = '/auth/callback') {
if (typeof window === 'undefined') return
const { state, codeVerifier, codeChallenge } = await generateOAuthParams()
// Store params for verification in callback
localStorage.setItem('oauth_state', state)
localStorage.setItem('oauth_code_verifier', codeVerifier)
// * Ensure clean URL construction without double slashes
const baseUrl = APP_URL.endsWith('/') ? APP_URL.slice(0, -1) : APP_URL
const path = redirectPath.startsWith('/') ? redirectPath : `/${redirectPath}`
const redirectUri = encodeURIComponent(`${baseUrl}${path}`)
const loginUrl = `${AUTH_URL}/login?client_id=pulse-app&redirect_uri=${redirectUri}&response_type=code&prompt=select_account&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`
window.location.href = loginUrl
}
export async function initiateSignupFlow(redirectPath = '/auth/callback') {
if (typeof window === 'undefined') return
const { state, codeVerifier, codeChallenge } = await generateOAuthParams()
// Store params for verification in callback
localStorage.setItem('oauth_state', state)
localStorage.setItem('oauth_code_verifier', codeVerifier)
// * Ensure clean URL construction without double slashes
const baseUrl = APP_URL.endsWith('/') ? APP_URL.slice(0, -1) : APP_URL
const path = redirectPath.startsWith('/') ? redirectPath : `/${redirectPath}`
const redirectUri = encodeURIComponent(`${baseUrl}${path}`)
const signupUrl = `${AUTH_URL}/signup?client_id=pulse-app&redirect_uri=${redirectUri}&response_type=code&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`
window.location.href = signupUrl
}