319 lines
6.4 KiB
TypeScript
319 lines
6.4 KiB
TypeScript
/**
|
|
* Identity implementation classes for Mail Manager services
|
|
*/
|
|
|
|
import type {
|
|
ServiceIdentity,
|
|
ServiceIdentityNone,
|
|
ServiceIdentityBasic,
|
|
ServiceIdentityToken,
|
|
ServiceIdentityOAuth,
|
|
ServiceIdentityCertificate
|
|
} from '@/types/service';
|
|
import { MutationProxy } from './mutation-proxy';
|
|
import { clonePlain } from './clone-plain';
|
|
|
|
/**
|
|
* Base Identity class
|
|
*/
|
|
export abstract class Identity<T extends ServiceIdentity = ServiceIdentity> {
|
|
protected _original: T;
|
|
protected _mutated: Partial<T>;
|
|
protected _mutationProxy: MutationProxy<T>;
|
|
protected _data: T;
|
|
|
|
protected constructor(initial: T) {
|
|
this._original = clonePlain(initial);
|
|
this._mutated = {};
|
|
this._mutationProxy = new MutationProxy<T>(() => this._original, () => this._mutated);
|
|
this._data = this._mutationProxy.create();
|
|
}
|
|
|
|
protected load(data: T): this {
|
|
this._original = clonePlain(data);
|
|
this._mutated = {};
|
|
this._data = this._mutationProxy.create();
|
|
return this;
|
|
}
|
|
|
|
toJSON(): ServiceIdentity {
|
|
return this.toJson();
|
|
}
|
|
|
|
toJson(): T;
|
|
toJson(delta: true): Partial<T>;
|
|
toJson(delta?: boolean): T | Partial<T> {
|
|
if (delta) {
|
|
return clonePlain(this._mutated);
|
|
}
|
|
|
|
return {
|
|
...clonePlain(this._original),
|
|
...clonePlain(this._mutated),
|
|
};
|
|
}
|
|
|
|
abstract clone(): Identity;
|
|
|
|
mutated(): boolean {
|
|
return Reflect.ownKeys(this._mutated).length > 0;
|
|
}
|
|
|
|
static fromJson(data: ServiceIdentity): Identity {
|
|
switch (data.type) {
|
|
case 'NA':
|
|
return IdentityNone.fromJson(data);
|
|
case 'BA':
|
|
return IdentityBasic.fromJson(data);
|
|
case 'TA':
|
|
return IdentityToken.fromJson(data);
|
|
case 'OA':
|
|
return IdentityOAuth.fromJson(data);
|
|
case 'CC':
|
|
return IdentityCertificate.fromJson(data);
|
|
default:
|
|
throw new Error(`Unknown identity type: ${(data as any).type}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* No authentication
|
|
*/
|
|
export class IdentityNone extends Identity<ServiceIdentityNone> {
|
|
|
|
constructor() {
|
|
super({
|
|
type: 'NA'
|
|
});
|
|
}
|
|
|
|
static fromJson(_data: ServiceIdentityNone): IdentityNone {
|
|
return new IdentityNone();
|
|
}
|
|
|
|
clone(): IdentityNone {
|
|
return IdentityNone.fromJson(this.toJson());
|
|
}
|
|
|
|
get type(): 'NA' {
|
|
return this._data.type;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Basic authentication (username/password)
|
|
*/
|
|
export class IdentityBasic extends Identity<ServiceIdentityBasic> {
|
|
|
|
constructor(identity: string = '', secret: string = '') {
|
|
super({
|
|
type: 'BA',
|
|
identity,
|
|
secret
|
|
});
|
|
}
|
|
|
|
static fromJson(data: ServiceIdentityBasic): IdentityBasic {
|
|
return new IdentityBasic().load(data);
|
|
}
|
|
|
|
clone(): IdentityBasic {
|
|
return IdentityBasic.fromJson(this.toJson());
|
|
}
|
|
|
|
get type(): 'BA' {
|
|
return this._data.type;
|
|
}
|
|
|
|
get identity(): string {
|
|
return this._data.identity;
|
|
}
|
|
|
|
set identity(value: string) {
|
|
this._data.identity = value;
|
|
}
|
|
|
|
get secret(): string {
|
|
return this._data.secret;
|
|
}
|
|
|
|
set secret(value: string) {
|
|
this._data.secret = value;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Token authentication (API key, static token)
|
|
*/
|
|
export class IdentityToken extends Identity<ServiceIdentityToken> {
|
|
|
|
constructor(token: string = '') {
|
|
super({
|
|
type: 'TA',
|
|
token
|
|
});
|
|
}
|
|
|
|
static fromJson(data: ServiceIdentityToken): IdentityToken {
|
|
return new IdentityToken().load(data);
|
|
}
|
|
|
|
clone(): IdentityToken {
|
|
return IdentityToken.fromJson(this.toJson());
|
|
}
|
|
|
|
get type(): 'TA' {
|
|
return this._data.type;
|
|
}
|
|
|
|
get token(): string {
|
|
return this._data.token;
|
|
}
|
|
|
|
set token(value: string) {
|
|
this._data.token = value;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* OAuth authentication
|
|
*/
|
|
export class IdentityOAuth extends Identity<ServiceIdentityOAuth> {
|
|
|
|
constructor(
|
|
accessToken: string = '',
|
|
accessScope?: string[],
|
|
accessExpiry?: number,
|
|
refreshToken?: string,
|
|
refreshLocation?: string
|
|
) {
|
|
super({
|
|
type: 'OA',
|
|
accessToken,
|
|
accessScope,
|
|
accessExpiry,
|
|
refreshToken,
|
|
refreshLocation
|
|
});
|
|
}
|
|
|
|
static fromJson(data: ServiceIdentityOAuth): IdentityOAuth {
|
|
return new IdentityOAuth().load(data);
|
|
}
|
|
|
|
clone(): IdentityOAuth {
|
|
return IdentityOAuth.fromJson(this.toJson());
|
|
}
|
|
|
|
isExpired(): boolean {
|
|
if (!this.accessExpiry) return false;
|
|
return Date.now() / 1000 >= this.accessExpiry;
|
|
}
|
|
|
|
expiresIn(): number {
|
|
if (!this.accessExpiry) return Infinity;
|
|
return Math.max(0, this.accessExpiry - Date.now() / 1000);
|
|
}
|
|
|
|
get type(): 'OA' {
|
|
return this._data.type;
|
|
}
|
|
|
|
get accessToken(): string {
|
|
return this._data.accessToken;
|
|
}
|
|
|
|
set accessToken(value: string) {
|
|
this._data.accessToken = value;
|
|
}
|
|
|
|
get accessScope(): string[] | undefined {
|
|
return this._data.accessScope ? [...this._data.accessScope] : undefined;
|
|
}
|
|
|
|
set accessScope(value: string[] | undefined) {
|
|
this._data.accessScope = value ? [...value] : undefined;
|
|
}
|
|
|
|
get accessExpiry(): number | undefined {
|
|
return this._data.accessExpiry;
|
|
}
|
|
|
|
set accessExpiry(value: number | undefined) {
|
|
this._data.accessExpiry = value;
|
|
}
|
|
|
|
get refreshToken(): string | undefined {
|
|
return this._data.refreshToken;
|
|
}
|
|
|
|
set refreshToken(value: string | undefined) {
|
|
this._data.refreshToken = value;
|
|
}
|
|
|
|
get refreshLocation(): string | undefined {
|
|
return this._data.refreshLocation;
|
|
}
|
|
|
|
set refreshLocation(value: string | undefined) {
|
|
this._data.refreshLocation = value;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Client certificate authentication (mTLS)
|
|
*/
|
|
export class IdentityCertificate extends Identity<ServiceIdentityCertificate> {
|
|
|
|
constructor(certificate: string = '', privateKey: string = '', passphrase?: string) {
|
|
super({
|
|
type: 'CC',
|
|
certificate,
|
|
privateKey,
|
|
passphrase
|
|
});
|
|
}
|
|
|
|
static fromJson(data: ServiceIdentityCertificate): IdentityCertificate {
|
|
return new IdentityCertificate().load(data);
|
|
}
|
|
|
|
clone(): IdentityCertificate {
|
|
return IdentityCertificate.fromJson(this.toJson());
|
|
}
|
|
|
|
get type(): 'CC' {
|
|
return this._data.type;
|
|
}
|
|
|
|
get certificate(): string {
|
|
return this._data.certificate;
|
|
}
|
|
|
|
set certificate(value: string) {
|
|
this._data.certificate = value;
|
|
}
|
|
|
|
get privateKey(): string {
|
|
return this._data.privateKey;
|
|
}
|
|
|
|
set privateKey(value: string) {
|
|
this._data.privateKey = value;
|
|
}
|
|
|
|
get passphrase(): string | undefined {
|
|
return this._data.passphrase;
|
|
}
|
|
|
|
set passphrase(value: string | undefined) {
|
|
this._data.passphrase = value;
|
|
}
|
|
|
|
}
|