feat: lots more improvements
Some checks failed
JS Unit Tests / test (pull_request) Failing after 29s
Build Test / test (pull_request) Successful in 31s
PHP Unit Tests / test (pull_request) Successful in 1m12s

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-04-25 15:41:16 -04:00
parent 86e4772d45
commit 99a68737d1
26 changed files with 902 additions and 596 deletions

View File

@@ -10,18 +10,55 @@ import type {
ServiceIdentityOAuth,
ServiceIdentityCertificate
} from '@/types/service';
import { MutationProxy } from './mutation-proxy';
import { clonePlain } from './clone-plain';
/**
* Base Identity class
*/
export abstract class Identity {
abstract toJson(): ServiceIdentity;
abstract clone(): Identity;
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':
@@ -43,48 +80,47 @@ export abstract class Identity {
/**
* No authentication
*/
export class IdentityNone extends Identity {
private _data: ServiceIdentityNone;
export class IdentityNone extends Identity<ServiceIdentityNone> {
constructor() {
super();
this._data = {
super({
type: 'NA'
};
}
get type(): 'NA' {
return this._data.type;
});
}
static fromJson(_data: ServiceIdentityNone): IdentityNone {
return new IdentityNone();
}
toJson(): ServiceIdentityNone {
return { ...this._data };
}
clone(): IdentityNone {
return IdentityNone.fromJson(this.toJson());
}
get type(): 'NA' {
return this._data.type;
}
}
/**
* Basic authentication (username/password)
*/
export class IdentityBasic extends Identity {
private _data: ServiceIdentityBasic;
export class IdentityBasic extends Identity<ServiceIdentityBasic> {
constructor(identity: string = '', secret: string = '') {
super();
this._data = {
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' {
@@ -107,32 +143,26 @@ export class IdentityBasic extends Identity {
this._data.secret = value;
}
static fromJson(data: ServiceIdentityBasic): IdentityBasic {
return new IdentityBasic(data.identity, data.secret);
}
toJson(): ServiceIdentityBasic {
return { ...this._data };
}
clone(): IdentityBasic {
return IdentityBasic.fromJson(this.toJson());
}
}
/**
* Token authentication (API key, static token)
*/
export class IdentityToken extends Identity {
private _data: ServiceIdentityToken;
export class IdentityToken extends Identity<ServiceIdentityToken> {
constructor(token: string = '') {
super();
this._data = {
super({
type: 'TA',
token
};
});
}
static fromJson(data: ServiceIdentityToken): IdentityToken {
return new IdentityToken().load(data);
}
clone(): IdentityToken {
return IdentityToken.fromJson(this.toJson());
}
get type(): 'TA' {
@@ -147,25 +177,12 @@ export class IdentityToken extends Identity {
this._data.token = value;
}
static fromJson(data: ServiceIdentityToken): IdentityToken {
return new IdentityToken(data.token);
}
toJson(): ServiceIdentityToken {
return { ...this._data };
}
clone(): IdentityToken {
return IdentityToken.fromJson(this.toJson());
}
}
/**
* OAuth authentication
*/
export class IdentityOAuth extends Identity {
private _data: ServiceIdentityOAuth;
export class IdentityOAuth extends Identity<ServiceIdentityOAuth> {
constructor(
accessToken: string = '',
@@ -174,15 +191,32 @@ export class IdentityOAuth extends Identity {
refreshToken?: string,
refreshLocation?: string
) {
super();
this._data = {
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' {
@@ -198,11 +232,11 @@ export class IdentityOAuth extends Identity {
}
get accessScope(): string[] | undefined {
return this._data.accessScope;
return this._data.accessScope ? [...this._data.accessScope] : undefined;
}
set accessScope(value: string[] | undefined) {
this._data.accessScope = value;
this._data.accessScope = value ? [...value] : undefined;
}
get accessExpiry(): number | undefined {
@@ -229,56 +263,28 @@ export class IdentityOAuth extends Identity {
this._data.refreshLocation = value;
}
static fromJson(data: ServiceIdentityOAuth): IdentityOAuth {
return new IdentityOAuth(
data.accessToken,
data.accessScope,
data.accessExpiry,
data.refreshToken,
data.refreshLocation
);
}
toJson(): ServiceIdentityOAuth {
return {
type: this.type,
accessToken: this.accessToken,
...(this.accessScope !== undefined && { accessScope: [...this.accessScope] }),
...(this.accessExpiry !== undefined && { accessExpiry: this.accessExpiry }),
...(this.refreshToken !== undefined && { refreshToken: this.refreshToken }),
...(this.refreshLocation !== undefined && { refreshLocation: this.refreshLocation })
};
}
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);
}
}
/**
* Client certificate authentication (mTLS)
*/
export class IdentityCertificate extends Identity {
private _data: ServiceIdentityCertificate;
export class IdentityCertificate extends Identity<ServiceIdentityCertificate> {
constructor(certificate: string = '', privateKey: string = '', passphrase?: string) {
super();
this._data = {
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' {
@@ -309,24 +315,4 @@ export class IdentityCertificate extends Identity {
this._data.passphrase = value;
}
static fromJson(data: ServiceIdentityCertificate): IdentityCertificate {
return new IdentityCertificate(
data.certificate,
data.privateKey,
data.passphrase
);
}
toJson(): ServiceIdentityCertificate {
return {
type: this.type,
certificate: this.certificate,
privateKey: this.privateKey,
...(this.passphrase !== undefined && { passphrase: this.passphrase })
};
}
clone(): IdentityCertificate {
return IdentityCertificate.fromJson(this.toJson());
}
}