casualroom/apps/fenghuo/web/lib/auth/oidc-client.ts

222 lines
6.4 KiB
TypeScript
Raw Permalink Normal View History

2025-07-28 07:50:50 +08:00
import { Prisma } from "@fenghuo/db";
// apps/web/lib/oidc-client.ts
interface OIDCTokenResponse {
access_token: string;
token_type: string;
expires_in: number;
refresh_token?: string;
id_token?: string;
scope: string;
}
interface OIDCUserInfo {
sub: string;
username: string;
email: string;
name: string;
department?: string;
roles?: string[];
[key: string]: any;
}
interface OIDCError {
error: string;
error_description?: string;
}
export class OIDCClient {
private baseUrl: string;
private clientId: string;
private clientSecret?: string;
constructor() {
this.baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; // 这里配置oidc服务器的地址
this.clientId = process.env.NEXT_PUBLIC_OIDC_CLIENT_ID || 'web-app'; // 这里配置对应的id
this.clientSecret = process.env.NEXT_PUBLIC_OIDC_CLIENT_SECRET;
}
/**
* 使访
*/
async loginWithPassword(username: string, password: string): Promise<OIDCTokenResponse> {
const response = await fetch(`${this.baseUrl}/auth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'password',
username,
password,
client_id: this.clientId,
scope: 'openid profile email',
}),
});
if (!response.ok) {
const error: OIDCError = await response.json();
throw new Error(error.error_description || error.error || '登录失败');
}
return response.json();
}
/**
* 使访
*/
async refreshToken(refreshToken: string): Promise<OIDCTokenResponse> {
const response = await fetch(`${this.baseUrl}/auth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': this.clientSecret ?
`Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}` : '',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: this.clientId,
}),
});
if (!response.ok) {
const error: OIDCError = await response.json();
throw new Error(error.error_description || error.error || '刷新令牌失败');
}
return response.json();
}
/**
*
*/
async getUserInfo(accessToken: string): Promise<OIDCUserInfo> {
const response = await fetch(`${this.baseUrl}/auth/userinfo`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const error: OIDCError = await response.json();
throw new Error(error.error_description || error.error || '获取用户信息失败');
}
return response.json();
}
/**
*
*/
async logout(accessToken?: string): Promise<void> {
if (accessToken) {
await fetch(`${this.baseUrl}/auth/revoke`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${accessToken}`,
},
body: new URLSearchParams({
token: accessToken,
token_type_hint: 'access_token',
}),
});
}
}
/**
*
*/
async register(userData: Prisma.UserCreateArgs): Promise<{ success: boolean; message: string }> {
const response = await fetch(`${this.baseUrl}/trpc/user.create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
json: userData,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || '注册失败');
}
const result = await response.json();
return {
success: true,
message: '注册成功',
};
}
/**
* URL
*/
buildAuthorizationUrl(options: {
redirectUri: string;
state?: string;
scope?: string;
nonce?: string;
}): string {
const { redirectUri, state = this.generateState(), scope = 'openid profile email', nonce = this.generateNonce() } = options;
const params = new URLSearchParams({
response_type: 'code',
client_id: this.clientId,
redirect_uri: redirectUri,
scope,
state,
nonce,
});
return `${this.baseUrl}/auth/auth?${params.toString()}`;
}
/**
*
*/
async handleAuthorizationCallback(
authorizationCode: string,
redirectUri: string,
state?: string
): Promise<OIDCTokenResponse> {
const response = await fetch(`${this.baseUrl}/auth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: redirectUri,
client_id: this.clientId,
...(this.clientSecret && { client_secret: this.clientSecret }),
}),
});
if (!response.ok) {
const error: OIDCError = await response.json();
throw new Error(error.error_description || error.error || '授权码交换失败');
}
return response.json();
}
/**
* CSRF
*/
private generateState(): string {
return btoa(Math.random().toString(36).substr(2, 9));
}
/**
* nonce
*/
private generateNonce(): string {
return btoa(Math.random().toString(36).substr(2, 9));
}
}
export const oidcClient = new OIDCClient();