authentication authorization

This commit is contained in:
2025-12-26 15:32:22 +01:00
parent 68ae9da22a
commit 64377de548
54 changed files with 2833 additions and 95 deletions

View File

@@ -0,0 +1,28 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type { ErrorReporter } from '../../interfaces/ErrorReporter';
import type { Logger } from '../../interfaces/Logger';
export type OperationalMode = 'normal' | 'maintenance' | 'test';
export type FeatureState = 'enabled' | 'disabled' | 'coming_soon' | 'hidden';
export type PolicySnapshotDto = {
policyVersion: number;
operationalMode: OperationalMode;
maintenanceAllowlist: {
view: string[];
mutate: string[];
};
capabilities: Record<string, FeatureState>;
loadedFrom: 'env' | 'file' | 'defaults';
loadedAtIso: string;
};
export class PolicyApiClient extends BaseApiClient {
constructor(baseUrl: string, errorReporter: ErrorReporter, logger: Logger) {
super(baseUrl, errorReporter, logger);
}
getSnapshot(): Promise<PolicySnapshotDto> {
return this.get<PolicySnapshotDto>('/policy/snapshot');
}
}

View File

@@ -0,0 +1,66 @@
import { Blocker } from './Blocker';
import type { PolicySnapshotDto } from '../api/policy/PolicyApiClient';
import { PolicyService } from '../services/policy/PolicyService';
export type CapabilityBlockReason = 'loading' | 'enabled' | 'coming_soon' | 'disabled' | 'hidden';
export class CapabilityBlocker extends Blocker {
private snapshot: PolicySnapshotDto | null = null;
constructor(
private readonly policyService: PolicyService,
private readonly capabilityKey: string,
) {
super();
}
updateSnapshot(snapshot: PolicySnapshotDto | null): void {
this.snapshot = snapshot;
}
canExecute(): boolean {
return this.getReason() === 'enabled';
}
getReason(): CapabilityBlockReason {
if (!this.snapshot) {
return 'loading';
}
return this.policyService.getCapabilityState(this.snapshot, this.capabilityKey);
}
block(): void {
this.snapshot = {
...(this.snapshot ?? {
policyVersion: 0,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date().toISOString(),
}),
capabilities: {
...(this.snapshot?.capabilities ?? {}),
[this.capabilityKey]: 'disabled',
},
};
}
release(): void {
this.snapshot = {
...(this.snapshot ?? {
policyVersion: 0,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date().toISOString(),
}),
capabilities: {
...(this.snapshot?.capabilities ?? {}),
[this.capabilityKey]: 'enabled',
},
};
}
}

View File

@@ -1,3 +1,4 @@
export { Blocker } from './Blocker';
export { CapabilityBlocker } from './CapabilityBlocker';
export { SubmitBlocker } from './SubmitBlocker';
export { ThrottleBlocker } from './ThrottleBlocker';

View File

@@ -9,6 +9,7 @@ import { AuthApiClient } from '../api/auth/AuthApiClient';
import { AnalyticsApiClient } from '../api/analytics/AnalyticsApiClient';
import { MediaApiClient } from '../api/media/MediaApiClient';
import { DashboardApiClient } from '../api/dashboard/DashboardApiClient';
import { PolicyApiClient } from '../api/policy/PolicyApiClient';
import { ProtestsApiClient } from '../api/protests/ProtestsApiClient';
import { PenaltiesApiClient } from '../api/penalties/PenaltiesApiClient';
import { PenaltyService } from './penalties/PenaltyService';
@@ -42,6 +43,7 @@ import { MembershipFeeService } from './payments/MembershipFeeService';
import { AuthService } from './auth/AuthService';
import { SessionService } from './auth/SessionService';
import { ProtestService } from './protests/ProtestService';
import { PolicyService } from './policy/PolicyService';
import { OnboardingService } from './onboarding/OnboardingService';
/**
@@ -67,6 +69,7 @@ export class ServiceFactory {
analytics: AnalyticsApiClient;
media: MediaApiClient;
dashboard: DashboardApiClient;
policy: PolicyApiClient;
protests: ProtestsApiClient;
penalties: PenaltiesApiClient;
};
@@ -85,6 +88,7 @@ export class ServiceFactory {
analytics: new AnalyticsApiClient(baseUrl, this.errorReporter, this.logger),
media: new MediaApiClient(baseUrl, this.errorReporter, this.logger),
dashboard: new DashboardApiClient(baseUrl, this.errorReporter, this.logger),
policy: new PolicyApiClient(baseUrl, this.errorReporter, this.logger),
protests: new ProtestsApiClient(baseUrl, this.errorReporter, this.logger),
penalties: new PenaltiesApiClient(baseUrl, this.errorReporter, this.logger),
};
@@ -237,12 +241,19 @@ export class ServiceFactory {
}
/**
* Create DashboardService instance
* Create PolicyService instance
*/
createDashboardService(): DashboardService {
return new DashboardService(this.apiClients.dashboard);
createPolicyService(): PolicyService {
return new PolicyService(this.apiClients.policy);
}
/**
* Create DashboardService instance
*/
createDashboardService(): DashboardService {
return new DashboardService(this.apiClients.dashboard);
}
/**
* Create MediaService instance
*/

View File

@@ -31,6 +31,7 @@ import { SponsorshipService } from './sponsors/SponsorshipService';
import { TeamJoinService } from './teams/TeamJoinService';
import { TeamService } from './teams/TeamService';
import { OnboardingService } from './onboarding/OnboardingService';
import { PolicyService } from './policy/PolicyService';
import { LandingService } from './landing/LandingService';
export interface Services {
@@ -60,6 +61,7 @@ export interface Services {
protestService: ProtestService;
penaltyService: PenaltyService;
onboardingService: OnboardingService;
policyService: PolicyService;
landingService: LandingService;
}
@@ -109,6 +111,7 @@ export function ServiceProvider({ children }: ServiceProviderProps) {
protestService: serviceFactory.createProtestService(),
penaltyService: serviceFactory.createPenaltyService(),
onboardingService: serviceFactory.createOnboardingService(),
policyService: serviceFactory.createPolicyService(),
landingService: serviceFactory.createLandingService(),
};
}, []);

View File

@@ -0,0 +1,17 @@
import type { FeatureState, PolicyApiClient, PolicySnapshotDto } from '../../api/policy/PolicyApiClient';
export class PolicyService {
constructor(private readonly apiClient: PolicyApiClient) {}
getSnapshot(): Promise<PolicySnapshotDto> {
return this.apiClient.getSnapshot();
}
getCapabilityState(snapshot: PolicySnapshotDto, capabilityKey: string): FeatureState {
return snapshot.capabilities[capabilityKey] ?? 'hidden';
}
isCapabilityEnabled(snapshot: PolicySnapshotDto, capabilityKey: string): boolean {
return this.getCapabilityState(snapshot, capabilityKey) === 'enabled';
}
}