Files
certd/packages/ui/certd-server/src/plugins/plugin-oauth/oidc/plugin-oidc.ts
2025-11-30 01:13:55 +08:00

133 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
import { BuildLoginUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
@IsAddon({
addonType: "oauth",
name: 'oidc',
title: 'OIDC认证',
desc: 'OpenID Connect 认证,统一认证服务',
icon:"simple-icons:fusionauth",
showTest: false,
})
export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
@AddonInput({
title: "ClientId",
helper: "ClientId / appId",
required: true,
})
clientId = "";
@AddonInput({
title: "ClientSecretKey",
component: {
placeholder: "ClientSecretKey / appSecretKey",
},
required: true,
})
clientSecretKey = "";
@AddonInput({
title: "服务地址",
helper: "Issuer地址去掉/.well-known/openid-configuration的服务发现地址",
component: {
placeholder: "https://oidc.example.com/oidc",
},
required: true,
})
issuerUrl = "";
async getClient() {
const client = await import('openid-client')
let server = new URL(this.issuerUrl)// Authorization Server's Issuer Identifier
let config = await client.discovery(
server,
this.clientId,
this.clientSecretKey,
)
// console.log(config.serverMetadata())
return {
config,
client
}
}
async buildLoginUrl(params: BuildLoginUrlReq) {
const { config, client } = await this.getClient()
let redirect_uri = new URL(params.redirectUri)
let scope = 'openid profile' // Scope of the access request
/**
* PKCE: The following MUST be generated for every redirect to the
* authorization_endpoint. You must store the code_verifier and state in the
* end-user session such that it can be recovered as the user gets redirected
* from the authorization server back to your application.
*/
let code_verifier = client.randomPKCECodeVerifier()
let code_challenge = await client.calculatePKCECodeChallenge(code_verifier)
let state:any = {
forType: params.forType || 'login',
}
state = this.ctx.utils.hash.base64(JSON.stringify(state))
let parameters: any = {
redirect_uri,
scope,
code_challenge,
code_challenge_method: 'S256',
state,
}
// if (!config.serverMetadata().supportsPKCE()) {
// /**
// * We cannot be sure the server supports PKCE so we're going to use state too.
// * Use of PKCE is backwards compatible even if the AS doesn't support it which
// * is why we're using it regardless. Like PKCE, random state must be generated
// * for every redirect to the authorization_endpoint.
// */
// parameters.state = client.randomState()
// }
let redirectTo = client.buildAuthorizationUrl(config, parameters)
return {
loginUrl: redirectTo.href,
ticketValue: {
codeVerifier: code_verifier,
state,
},
};
}
async onCallback(req: OnCallbackReq) {
const { config, client } = await this.getClient()
let tokens: any = await client.authorizationCodeGrant(
config,
req.currentURL,
{
expectedState: client.skipStateCheck ,
pkceCodeVerifier: req.ticketValue.codeVerifier,
}
)
const claims = tokens.claims()
return {
token:{
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
},
userInfo: {
openId: claims.sub,
nickName: claims.nickname || claims.preferred_username || "",
avatar: claims.picture,
},
}
};
}