sessionize-auth
Version:
A flexible session management library for React, Next.js, Angular, and React Native
187 lines (168 loc) • 5.45 kB
text/typescript
import { SSOProvider, SSOProviderConfig, SSOUser } from "../types";
/**
* Base SSO Provider class
*/
export abstract class BaseSSOProvider {
protected config: SSOProviderConfig;
protected providerId: string;
constructor(providerId: string, config: SSOProviderConfig) {
this.providerId = providerId;
this.config = config;
}
/**
* Get the provider configuration
*/
getProvider(): SSOProvider {
return {
id: this.providerId,
name: this.getProviderName(),
icon: this.getProviderIcon(),
color: this.getProviderColor(),
scopes: this.getScopes(),
authUrl: this.getAuthUrl(),
tokenUrl: this.getTokenUrl(),
userInfoUrl: this.getUserInfoUrl(),
clientId: this.config.clientId,
redirectUri: this.config.redirectUri
};
}
/**
* Generate authorization URL
*/
generateAuthUrl(state: string, returnTo?: string): string {
const params = new URLSearchParams({
client_id: this.config.clientId,
redirect_uri: this.config.redirectUri,
response_type: 'code',
scope: this.getScopes().join(' '),
state: state,
...this.getCustomParams()
});
if (returnTo) {
params.set('return_to', returnTo);
}
return `${this.getAuthUrl()}?${params.toString()}`;
}
/**
* Exchange authorization code for access token
*/
async exchangeCodeForToken(code: string, state: string): Promise<{ accessToken: string; refreshToken?: string; expiresIn?: number }> {
try {
const response = await fetch(this.getTokenUrl(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: new URLSearchParams({
client_id: this.config.clientId,
client_secret: this.config.clientSecret || '',
code: code,
grant_type: 'authorization_code',
redirect_uri: this.config.redirectUri
})
});
if (!response.ok) {
throw new Error(`Token exchange failed: ${response.statusText}`);
}
const data = await response.json();
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in
};
} catch (error) {
throw new SSOErrorClass({
code: 'TOKEN_EXCHANGE_FAILED',
message: 'Failed to exchange authorization code for access token',
provider: this.providerId,
details: error
});
}
}
/**
* Get user information from provider
*/
async getUserInfo(accessToken: string): Promise<SSOUser> {
try {
const response = await fetch(this.getUserInfoUrl(), {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to get user info: ${response.statusText}`);
}
const data = await response.json();
return this.parseUserInfo(data, accessToken);
} catch (error) {
throw new SSOErrorClass({
code: 'USER_INFO_FAILED',
message: 'Failed to get user information',
provider: this.providerId,
details: error
});
}
}
/**
* Refresh access token
*/
async refreshToken(refreshToken: string): Promise<{ accessToken: string; expiresIn?: number }> {
try {
const response = await fetch(this.getTokenUrl(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: new URLSearchParams({
client_id: this.config.clientId,
client_secret: this.config.clientSecret || '',
refresh_token: refreshToken,
grant_type: 'refresh_token'
})
});
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.statusText}`);
}
const data = await response.json();
return {
accessToken: data.access_token,
expiresIn: data.expires_in
};
} catch (error) {
throw new SSOErrorClass({
code: 'TOKEN_REFRESH_FAILED',
message: 'Failed to refresh access token',
provider: this.providerId,
details: error
});
}
}
// Abstract methods to be implemented by specific providers
protected abstract getProviderName(): string;
protected abstract getProviderIcon(): string;
protected abstract getProviderColor(): string;
protected abstract getScopes(): string[];
protected abstract getAuthUrl(): string;
protected abstract getTokenUrl(): string;
protected abstract getUserInfoUrl(): string;
protected abstract parseUserInfo(data: any, accessToken: string): SSOUser;
protected abstract getCustomParams(): Record<string, string>;
}
/**
* SSO Error class
*/
export class SSOErrorClass extends Error {
public code: string;
public provider?: string;
public details?: any;
constructor(error: { code: string; message: string; provider?: string; details?: any }) {
super(error.message);
this.name = 'SSOError';
this.code = error.code;
this.provider = error.provider;
this.details = error.details;
}
}