UNPKG

kaven-utils

Version:

Utils for Node.js.

214 lines (213 loc) 7.74 kB
/******************************************************************** * @author: Kaven * @email: kaven@wuwenkai.com * @website: http://blog.kaven.xyz * @file: [Kaven-Utils] /src/net/sso/KavenSSOServer.ts * @create: 2019-02-21 11:27:41.259 * @modify: 2025-10-14 22:58:04.827 * @version: 6.1.0 * @times: 76 * @lines: 283 * @copyright: Copyright © 2019-2025 Kaven. All Rights Reserved. * @description: [description] * @license: [license] ********************************************************************/ import { DecodeByRFC3986, GenerateGuid, ParseQueryParameters } from "kaven-basic"; import { ApiRequestEx } from "../../ApiRequestEx.js"; import { HttpGet } from "../../KavenUtility.Net.js"; import { KavenSSO, SSOAction } from "./KavenSSO.js"; export class KavenSSOServer extends KavenSSO { LoginPath = "/login"; SSOPath = "/sso"; /** * Server logged in sessions */ LoginSessionSet; // <token, client> clientsMap = new Map(); Logger; constructor(secret, options) { super(secret, options?.parameterName, options?.tokenName); if (options?.loginPath) { this.LoginPath = options.loginPath; } if (options?.ssoPath) { this.SSOPath = options.ssoPath; } if (options?.sessionSet) { this.LoginSessionSet = options.sessionSet; } else { this.LoginSessionSet = new Set(); } } GetClients(appID, sessionID) { const array = []; for (const item of Array.from(this.clientsMap.values())) { if (item.appID === appID) { if (sessionID) { if (sessionID === item.sessionID) { array.push(item); break; // client with same appID and sessionID is unique } } } } return array; } GetClientsByServerSessionID(serverSessionID) { const array = []; for (const item of Array.from(this.clientsMap.values())) { if (item.serverSessionID === serverSessionID) { array.push(item); } } return array; } GetClient(token) { return this.clientsMap.get(token); } /** * * @param originalUrl * @param userID * @param loginSessionID If defined, a new client will be created */ async Verify(originalUrl, userID, loginSessionID) { // verify request query const apiResult = await ApiRequestEx.VerifyByHMAC_SHA1(this.Secret, originalUrl); if (apiResult !== true) { this.Logger?.Warn(apiResult); return undefined; } const queryParameters = ParseQueryParameters(originalUrl, DecodeByRFC3986); const result = { parameters: queryParameters, }; try { const action = queryParameters[this.ParameterName.Action]; if (action) { result.action = action; } else { return result; } const appID = queryParameters[this.ParameterName.AppID]; if (!appID) { this.Logger?.Warn(`AppID: ${appID}`); return result; } const token = queryParameters[this.ParameterName.Token]; const returnTo = queryParameters[this.ParameterName.ReturnTo]; const sessionID = queryParameters[this.ParameterName.SessionID]; const notifyTo = queryParameters[this.ParameterName.NotifyTo]; if (this.EnableLog) { this.Logger?.Info(`SSO request verified, appID: ${appID}, returnTo: ${returnTo}, sessionID: ${sessionID}`); } let client; if (token) { client = this.GetClient(token); } if (client === undefined) { // try to get client by sessionID if (sessionID !== undefined) { const clients = this.GetClients(appID, sessionID); if (clients.length > 0) { client = clients[0]; client.returnTo = returnTo; } } } if (client === undefined) { // new client if (loginSessionID !== undefined) { client = { appID: appID, returnTo, sessionID: sessionID, userID, serverSessionID: loginSessionID, notifyTo, token: GenerateGuid(), }; this.LoginSessionSet.add(loginSessionID); if (this.EnableLog) { this.Logger?.Info(`Add new SSO client: ${client}`); } } } if (client !== undefined) { this.clientsMap.set(client.token, client); client.returnTo = returnTo; result.client = client; } return result; } catch (ex) { this.Logger?.Error(ex); return result; } } MakeNotifyURL(client, action, data) { if (client.notifyTo) { const url = new ApiRequestEx(client.notifyTo) .AddParameter(this.ParameterName.Token, client.token) .AddParameter(this.ParameterName.AppID, client.appID) .AddParameter(this.ParameterName.Action, action) .AddParameter(this.ParameterName.UserID, client.userID) .AddParameter(this.ParameterName.Data, data) .MakeByHMAC_SHA1(this.Secret); return url; } return undefined; } async MakeRedirectURL(client) { if (!client.returnTo) { return undefined; } const url = await new ApiRequestEx(client.returnTo) .AddParameter(this.ParameterName.Action, SSOAction.Login) .AddParameter(this.ParameterName.Token, client.token) .AddParameter(this.ParameterName.AppID, client.appID) .AddParameter(this.ParameterName.UserID, client.userID) .MakeByHMAC_SHA1(this.Secret); return url; } async GetRedirectToClientURL(client) { if (!client) { return undefined; } const url = await this.MakeRedirectURL(client); return url; } DeleteToken(token) { if (this.clientsMap.delete(token)) { if (this.EnableLog) { this.Logger?.Info(`delete token: ${token}`); } return true; } return false; } async Logout(serverSessionID, ignoreToken) { const clients = this.GetClientsByServerSessionID(serverSessionID); // delete and notify other clients for (const client of clients) { this.DeleteToken(client.token); if (ignoreToken === client.token) { continue; } const url = await this.MakeNotifyURL(client, SSOAction.NotifyLogout); if (url) { HttpGet(url).then(res => { if (this.EnableLog) { this.Logger?.Info(`notify client[${client.appID}] logout: ${res.Status} - ${res.StatusText}`); } }).catch(ex => { this.Logger?.Error(ex); }); } } this.LoginSessionSet.delete(serverSessionID); } }