kaven-utils
Version:
Utils for Node.js.
158 lines (157 loc) • 5.99 kB
JavaScript
/********************************************************************
* @author: Kaven
* @email: kaven@wuwenkai.com
* @website: http://blog.kaven.xyz
* @file: [Kaven-Utils] /src/net/sso/KavenSSOClient.ts
* @create: 2019-02-21 11:27:41.261
* @modify: 2024-11-01 10:48:07.307
* @version: 5.4.5
* @times: 79
* @lines: 215
* @copyright: Copyright © 2019-2024 Kaven. All Rights Reserved.
* @description: [description]
* @license: [license]
********************************************************************/
import { DecodeByRFC3986, HttpStatusCode, RemoveQueryFromURL, ParseQueryParameters, Strings_Empty } from "kaven-basic";
import { ApiRequestEx } from "../../ApiRequestEx.js";
import { HttpGet } from "../../KavenUtility.Net.js";
import { KavenSSO, SSOAction, SSOVerifyError } from "./KavenSSO.js";
import { InternalLogger } from "../../KavenUtility.Internal.js";
export class KavenSSOClient extends KavenSSO {
// #region Private Fields
tokenSet = new Set();
// #endregion
// #region Readonly Fields
AppID = Strings_Empty;
AppHost = Strings_Empty;
AppNotifyPath = "/sso/notify";
ServerHost = Strings_Empty;
ServerLoginPath = "/login";
ServerSSOPath = "/sso";
// #endregion
// #region Public Fields
// #endregion
// #region Properties
get ServerLoginURL() {
return this.ServerHost + this.ServerLoginPath;
}
get ServerSSOURL() {
return this.ServerHost + this.ServerSSOPath;
}
get AppNotifyURL() {
return this.AppHost + this.AppNotifyPath;
}
// #endregion
// #region C'tor
constructor(appID, appHost, serverHost, secret, options) {
super(secret, options?.parameterName, options?.tokenName);
this.AppID = appID;
this.AppHost = appHost;
this.ServerHost = serverHost;
if (options?.serverLoginPath) {
this.ServerLoginPath = options.serverLoginPath;
}
if (options?.serverSSOPath) {
this.ServerSSOPath = options.serverSSOPath;
}
}
// #endregion
// #region Public Methods
MakeSSOURL(action, client, data) {
const url = new ApiRequestEx(this.ServerSSOURL)
.AddParameter(this.ParameterName.Action, action)
.AddParameter(this.ParameterName.AppID, this.AppID)
.AddParameter(this.ParameterName.NotifyTo, this.AppNotifyURL)
.AddParameter(this.ParameterName.SessionID, client.sessionID)
.AddParameter(this.ParameterName.Token, client.token)
.AddParameter(this.ParameterName.Data, data)
.MakeByHMAC_SHA1(this.Secret);
return url;
}
MakeRedirectURL(returnTo, sessionID) {
returnTo = this.RemoveSSOParametersFromURL(returnTo);
const url = new ApiRequestEx(this.ServerLoginURL)
.AddParameter(this.ParameterName.Action, SSOAction.Login)
.AddParameter(this.ParameterName.AppID, this.AppID)
.AddParameter(this.ParameterName.NotifyTo, this.AppNotifyURL)
.AddParameter(this.ParameterName.SessionID, sessionID)
.AddParameter(this.ParameterName.ReturnTo, returnTo)
.MakeByHMAC_SHA1(this.Secret);
return url;
}
RemoveSSOParametersFromURL(url, ...others) {
const result = RemoveQueryFromURL(url, ...Object.values(this.ParameterName), ApiRequestEx.Signature, ApiRequestEx.SignatureNonce, ApiRequestEx.Timestamp, ...others);
return result;
}
GetRedirectToServerURL(originalUrl, sessionID) {
const url = this.MakeRedirectURL(this.AppHost + originalUrl, sessionID);
return url;
}
async Verify(originalUrl) {
// verify request query
const apiResult = await ApiRequestEx.VerifyByHMAC_SHA1(this.Secret, originalUrl);
if (apiResult !== true) {
InternalLogger()?.Warn(apiResult);
return undefined;
}
const queryParameters = ParseQueryParameters(originalUrl, DecodeByRFC3986);
const result = {
parameters: queryParameters,
};
const action = queryParameters[this.ParameterName.Action];
if (action) {
result.action = action;
}
const userID = queryParameters[this.ParameterName.UserID];
if (userID) {
result.userID = userID;
}
return result;
}
async VerifyClient(client) {
if (this.tokenSet.has(client.token)) {
return true;
}
const url = await this.MakeSSOURL(SSOAction.Verify, client);
const response = await HttpGet(url);
// handle success
if (response.Status === HttpStatusCode.OK) {
this.tokenSet.add(client.token);
return true;
}
else {
// delete invalid token
if (response.Status === HttpStatusCode.BadRequest) {
InternalLogger()?.Warn(`remove verify failed token: ${client.token}`);
await this.Logout(client, false);
}
return SSOVerifyError.TokenInvalid;
}
}
async GetDataFromSSOServer(url) {
const r = await HttpGet(url);
return r.Json;
}
/**
* @note This method will not throw an exception
*/
async Logout(client, notifyServer = true) {
try {
if (this.tokenSet.delete(client.token)) {
if (this.EnableLog) {
InternalLogger()?.Info(`Logout, delete token: ${client.token}`);
}
}
if (notifyServer) {
const url = await this.MakeSSOURL(SSOAction.Logout, client);
const response = await HttpGet(url);
if (response.Status !== HttpStatusCode.OK) {
InternalLogger()?.Warn(response);
}
}
}
catch (ex) {
InternalLogger()?.Error(ex);
}
}
}