release: @marketplaces/auth 0.1.0 (5ffc1b1)

This commit is contained in:
2026-08-21 08:10:12 +04:00
parent 93f99cc7b1
commit f2c5893004
48 changed files with 1956 additions and 1578 deletions

545
dist/types/marketplaces-auth.d.ts vendored Normal file
View File

@@ -0,0 +1,545 @@
import * as _angular_core from '@angular/core';
import { InjectionToken, EnvironmentProviders } from '@angular/core';
import { HttpHeaders, HttpInterceptorFn } from '@angular/common/http';
import * as _angular_forms_signals from '@angular/forms/signals';
import { Observable } from 'rxjs';
import { CanActivateFn } from '@angular/router';
import * as _marketplaces_auth from '@marketplaces/auth';
interface MarketplacesAuthConfig {
/** Central auth service URL. It is not the tenant API URL. */
apiUrl: string;
/** Override only for SSR/custom-domain integrations. Browser default is location.hostname. */
marketplaceDomain?: string | (() => string);
telegramBotUsername?: string;
credentialsPath?: string;
yandexStartPath?: string;
yandexSessionPath?: string;
pollIntervalMs?: number;
}
/** Base URL for the auth backend, e.g. `https://api.example.com`. Provide from the consuming app's environment config. */
declare const AUTH_API_URL: InjectionToken<string>;
/** Telegram bot username used to build QR/deep-link login URLs. Optional — falls back to a default if not provided. */
declare const TELEGRAM_BOT_USERNAME: InjectionToken<string>;
declare const MARKETPLACES_AUTH_CONFIG: InjectionToken<MarketplacesAuthConfig>;
declare function provideMarketplacesAuth(config: MarketplacesAuthConfig): EnvironmentProviders;
declare const MARKETPLACE_DOMAIN_HEADER = "X-Marketplace-Domain";
declare function normalizeMarketplaceDomain(domain: string): string;
declare class AuthMarketplaceContext {
private readonly config;
domain(): string;
headers(extra?: Record<string, string>): HttpHeaders;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthMarketplaceContext, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthMarketplaceContext>;
}
interface AuthSession {
sessionId: string;
userId: number | null;
username: string | null;
displayName: string;
active: boolean;
expires: string;
}
interface WebSessionStart {
webSessionID: string;
url: string;
}
type AuthStatus = 'unknown' | 'checking' | 'authenticated' | 'expired' | 'unauthenticated';
type AdminAuthStatus = 'unknown' | 'checking' | 'authenticated' | 'expired' | 'unauthenticated';
type AuthMode = 'customer' | 'admin';
type AuthMethod = 'qr' | 'credentials' | 'yandex';
interface CredentialLogin {
login: string;
password: string;
}
interface AuthResult {
method: AuthMethod;
mode: AuthMode;
session: AuthSession;
accessToken?: string;
refreshToken?: string;
}
interface ExternalAuthStart {
attemptId: string;
authorizationUrl: string;
}
interface AuthFailure {
method: AuthMethod;
code: 'configuration' | 'invalid_credentials' | 'backend' | 'popup_blocked' | 'expired';
message: string;
cause?: unknown;
}
declare class MarketplacesAuthComponent {
readonly qr: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly credentials: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly yandex: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly mode: _angular_core.InputSignal<AuthMode>;
readonly title: _angular_core.InputSignal<string>;
readonly authenticated: _angular_core.OutputEmitterRef<AuthResult>;
readonly authError: _angular_core.OutputEmitterRef<AuthFailure>;
readonly cancelled: _angular_core.OutputEmitterRef<void>;
readonly method: _angular_core.WritableSignal<AuthMethod | null>;
readonly busy: _angular_core.WritableSignal<boolean>;
readonly error: _angular_core.WritableSignal<AuthFailure | null>;
readonly qrImage: _angular_core.WritableSignal<string | null>;
readonly externalUrl: _angular_core.WritableSignal<string | null>;
private readonly credentialsModel;
readonly credentialsForm: _angular_forms_signals.FieldTree<{
login: string;
password: string;
}, string | number, "writable">;
private readonly gateway;
private readonly config;
private poll?;
constructor();
select(method: AuthMethod | null): void;
startQr(): void;
loginWithCredentials(event: Event): void;
startYandex(): void;
private pollForQr;
private prepareQr;
private pollForYandex;
private begin;
private finish;
private fail;
private isFailure;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MarketplacesAuthComponent, never>;
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MarketplacesAuthComponent, "mp-auth, marketplaces-auth", never, { "qr": { "alias": "qr"; "required": false; "isSignal": true; }; "credentials": { "alias": "credentials"; "required": false; "isSignal": true; }; "yandex": { "alias": "yandex"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "authenticated": "authenticated"; "authError": "authError"; "cancelled": "cancelled"; }, never, never, true, never>;
}
interface MarketplacesAuthGateway {
startQr(mode: AuthMode): Observable<WebSessionStart>;
checkQr(mode: AuthMode, attemptId: string): Observable<AuthSession | null>;
loginWithCredentials(mode: AuthMode, credentials: CredentialLogin): Observable<AuthResult>;
startYandex(mode: AuthMode, returnUrl: string): Observable<ExternalAuthStart>;
checkYandex(mode: AuthMode, attemptId: string): Observable<AuthResult | null>;
}
declare const MARKETPLACES_AUTH_GATEWAY: InjectionToken<MarketplacesAuthGateway>;
declare class HttpMarketplacesAuthGateway implements MarketplacesAuthGateway {
private readonly http;
private readonly config;
private readonly context;
private readonly customerAuth;
private readonly adminAuth;
startQr(mode: AuthMode): Observable<WebSessionStart>;
checkQr(mode: AuthMode, attemptId: string): Observable<AuthSession | null>;
loginWithCredentials(mode: AuthMode, credentials: CredentialLogin): Observable<AuthResult>;
startYandex(mode: AuthMode, returnUrl: string): Observable<ExternalAuthStart>;
checkYandex(mode: AuthMode, attemptId: string): Observable<AuthResult | null>;
private accept;
private url;
private failure;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<HttpMarketplacesAuthGateway, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<HttpMarketplacesAuthGateway>;
}
/**
* The one Telegram QR/session API (`{authApiUrl}/users/sessions`). Customer
* login (AuthService) and admin login (AdminAuthService) both call this same
* service against this same endpoint - there is no separate admin backend.
* This class only does the HTTP call + response normalization; it holds no
* session state and writes no cookies, so each caller manages its own
* storage/signals independently on top of it.
*/
declare class TelegramSessionApiService {
private readonly http;
private readonly authApiUrl;
private readonly telegramBotUsername;
private readonly marketplaceContext;
createSession(): Observable<WebSessionStart>;
checkSessionOnce(webSessionID: string | null): Observable<AuthSession | null>;
logout(webSessionID: string): Observable<unknown>;
getBotLoginUrl(webSessionID: string): string;
getBotAppLoginUrl(webSessionID: string): string;
private getBotUsername;
private normalizeWebSession;
private extractSessionId;
private readFirst;
private readString;
private readNumber;
private asRecord;
private isActiveStatus;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TelegramSessionApiService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<TelegramSessionApiService>;
}
/** Customer-facing Telegram QR/session auth. Distinct storage/state from AdminAuthService by design. */
declare class AuthService$1 {
private readonly api;
private sessionSignal;
private statusSignal;
private showLoginSignal;
/** Current auth session */
readonly session: _angular_core.Signal<AuthSession | null>;
/** Current auth status */
readonly status: _angular_core.Signal<AuthStatus>;
/** Whether user is fully authenticated */
readonly isAuthenticated: _angular_core.Signal<boolean>;
/** Whether to show login dialog */
readonly showLoginDialog: _angular_core.Signal<boolean>;
/** Display name of authenticated user */
readonly displayName: _angular_core.Signal<string | null>;
private sessionCheckTimer?;
constructor();
/** Check the current webSessionID cookie against the auth backend. */
checkSession(): void;
/** Check session without updating internal state beyond activating on success (used for polling). */
checkSessionOnce(webSessionID?: string | null): Observable<AuthSession | null>;
/**
* Called after user completes Telegram login.
*/
onTelegramLoginComplete(): void;
/** Generate the Telegram login URL for bot-based auth */
getTelegramLoginUrl(webSessionID: string): string;
/** Generate a Telegram app deep link for mobile login without opening a browser tab. */
getTelegramAppLoginUrl(webSessionID: string): string;
/** Create a backend web session and return the Telegram start link for it. */
createWebSession(): Observable<WebSessionStart>;
/** Show login dialog (called when user tries to pay without being logged in) */
requestLogin(): void;
/** Hide login dialog */
hideLogin(): void;
/** Logout — clears session on backend and locally */
logout(): void;
/** Accept a session returned by credentials or an external provider. */
acceptSession(session: AuthSession): void;
private activateSession;
private clearAuthState;
/** Schedule a session re-check before it expires */
private scheduleSessionRefresh;
private clearSessionRefresh;
private getStoredWebSessionID;
private setStoredWebSessionID;
private clearStoredWebSessionID;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthService$1, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthService$1>;
}
declare class AdminAuthService {
private readonly api;
private readonly sessionSignal;
private readonly statusSignal;
private readonly showLoginSignal;
readonly session: _angular_core.Signal<AuthSession | null>;
readonly status: _angular_core.Signal<AdminAuthStatus>;
readonly isAuthenticated: _angular_core.Signal<boolean>;
readonly showLoginDialog: _angular_core.Signal<boolean>;
readonly displayName: _angular_core.Signal<string | null>;
private sessionCheckTimer?;
constructor();
checkSession(): void;
/** Check session without mutating internal state beyond activating on success (used for polling). */
checkSessionOnce(webSessionID?: string | null): Observable<AuthSession | null>;
/** Create a backend web session - identical call to the customer login (TelegramSessionApiService.createSession). */
createWebSession(): Observable<WebSessionStart>;
getAdminAppLoginUrl(webSessionID: string): string;
onLoginComplete(): void;
requestLogin(): void;
/**
* Dev-only shortcut for local testing without a reachable Telegram/session
* backend: fabricates a local session and activates it directly, skipping
* the QR flow entirely. No-ops in production builds (checked via Angular's
* isDevMode() at runtime, not just build-time, so it is safe even if this
* code ships). Never call this from anywhere reachable in a production build.
*/
devBypassLogin(): void;
hideLogin(): void;
logout(): void;
/** Accept a session/tokens returned by credentials or an external provider. */
acceptSession(session: AuthSession, token?: string, refreshToken?: string): void;
/** JWT pair storage, reserved for once the backend issues admin access/refresh tokens. Unused until then. */
getAdminToken(): string | null;
setAdminTokens(token: string, refreshToken: string): void;
clearAdminTokens(): void;
private activateSession;
private clearAuthState;
private scheduleSessionRefresh;
private clearSessionRefresh;
private getStoredAdminSessionID;
private setStoredAdminSessionID;
private clearStoredAdminSessionID;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AdminAuthService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AdminAuthService>;
}
/** Guards `/admin/**`-style routes. Never shares state with the customer auth guard/service. */
declare const adminAuthGuard: CanActivateFn;
/**
* Attaches admin session/token headers only to admin API requests. Scoped to
* admin-gated paths so it never touches customer requests and never reads
* the customer AuthService's session.
*/
declare const adminAuthHeadersInterceptor: HttpInterceptorFn;
/** Roles the Ed25519 JWT `role` claim is expected to carry. Ordered highest-to-lowest privilege; PermissionService does not rely on the order, it is documentation only. */
type AdminRole = 'Owner' | 'Administrator' | 'Editor' | 'Support' | 'ReadOnly';
/**
* Coarse-grained permission keys. Intentionally small and domain-agnostic -
* fine-grained, per-domain permissions stay server-side; the frontend only
* needs enough to hide/disable UI, never to be the source of truth for
* authorization.
*/
type Permission = 'backoffice.read' | 'backoffice.write' | 'builder.read' | 'builder.write' | 'users.manage' | 'settings.manage';
declare const ROLE_PERMISSIONS: Readonly<Record<AdminRole, readonly Permission[]>>;
/** Wire contracts for the Ed25519 challenge/response admin auth flow. */
interface AuthChallenge {
nonce: string;
/** ISO 8601 issue time of the challenge. */
issuedAt: string;
/** ISO 8601 - challenge must be used before this or the backend rejects it. */
expiresAt: string;
}
interface VerifySignatureRequest {
publicKey: string;
signature: string;
nonce: string;
}
interface AuthTokenPair {
token: string;
refreshToken: string;
}
interface RefreshTokenRequest {
refreshToken: string;
}
/**
* Claims expected in the JWT `token`. Decoded client-side for display/UX
* only (role-gating UI, expiry countdown) - the frontend never treats this
* as proof of authorization; every admin request is still re-checked
* server-side.
*/
interface JwtClaims {
sub: string;
role: AdminRole;
/** Issued-at, seconds since epoch (standard `iat` claim). */
iat: number;
/** Expiry, seconds since epoch (standard `exp` claim). */
exp: number;
publicKey: string;
}
/**
* Error codes the Ed25519 admin auth flow can surface to the UI. Each maps to
* a dedicated screen rather than a generic toast, because the recovery
* action differs per code (re-login vs. retry vs. wait).
*/
type AuthErrorCode = 'session-expired' | 'invalid-signature' | 'unauthorized' | 'forbidden' | 'backend-unavailable';
interface AuthError {
code: AuthErrorCode;
message: string;
/** HTTP status that produced this error, when known (absent for client-side errors, e.g. no Ed25519 support). */
status?: number;
}
declare function authErrorCodeFromBackendCode(code: unknown): AuthErrorCode | undefined;
/** Maps a backend HTTP status to the AuthErrorCode screen it should route to. */
declare function authErrorCodeFromStatus(status: number): AuthErrorCode;
type LoginPhase = 'idle' | 'requesting-challenge' | 'signing' | 'verifying' | 'done';
/**
* Orchestrates the Ed25519 challenge/response admin auth flow end to end:
*
* GET /api/admin/auth/challenge -> { nonce }
* sign(nonce) with local Ed25519 key -> signature
* POST /api/admin/auth/verify -> { token, refreshToken }
*
* This is the lowest-level orchestrator; components should go through
* AuthFacade rather than calling this directly. Exported from the package
* barrel as `Ed25519AuthService` to avoid colliding with the telegram
* module's `AuthService`.
*/
declare class AuthService {
private readonly api;
private readonly keypair;
private readonly session;
private readonly loginPhaseSignal;
private readonly lastErrorSignal;
readonly loginPhase: _angular_core.Signal<LoginPhase>;
readonly lastError: _angular_core.Signal<AuthError | null>;
constructor();
/** Restores a persisted session on app bootstrap. Call once from an APP_INITIALIZER or root component. */
restoreSession(): void;
login(): Observable<AuthTokenPair>;
refresh(): Observable<AuthTokenPair>;
logout(): Observable<void>;
private signChallenge;
private handleAuthError;
private toAuthErrorShape;
private toAuthError;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthService>;
}
/**
* Public surface for components/pages. Components should depend on this,
* not on AuthService/SessionService/PermissionService directly, so the
* orchestration details (which service owns what) can change without
* touching UI code.
*/
declare class AuthFacade {
private readonly auth;
private readonly session;
private readonly permissions;
private readonly router;
readonly isAuthenticated: _angular_core.Signal<boolean>;
readonly status: _angular_core.Signal<_marketplaces_auth.SessionStatus>;
readonly role: _angular_core.Signal<_marketplaces_auth.AdminRole | null>;
readonly loginPhase: _angular_core.Signal<_marketplaces_auth.LoginPhase>;
readonly lastError: _angular_core.Signal<_marketplaces_auth.AuthError | null>;
restoreSession(): void;
login(onSuccessRedirectTo?: string): void;
logout(redirectTo?: string): void;
can(permission: Permission): boolean;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthFacade, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthFacade>;
}
/**
* Thin HTTP client for the Ed25519 admin auth endpoints. These endpoints may
* not exist on every backend yet - calling them before the backend ships
* 404s or connection-errors, which AuthService maps to the
* `backend-unavailable` error screen. No mock/fake responses are fabricated
* here; this is real HttpClient wiring against the real contract.
*/
declare class AuthApiService {
private readonly http;
private readonly baseUrl;
requestChallenge(): Observable<AuthChallenge>;
verifySignature(request: VerifySignatureRequest): Observable<AuthTokenPair>;
refresh(request: RefreshTokenRequest): Observable<AuthTokenPair>;
logout(refreshToken: string): Observable<void>;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthApiService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthApiService>;
}
type SessionStatus = 'unknown' | 'restoring' | 'authenticated' | 'unauthenticated' | 'expired';
/**
* Holds the Ed25519-flow JWT/refresh-token pair and derived claims. Separate
* from the telegram module's AdminAuthService (Telegram-session state) by
* design - the two auth mechanisms are not merged until both ship on the
* same backend and a migration decision is made.
*/
declare class SessionService {
private readonly jwt;
private readonly tokenSignal;
private readonly refreshTokenSignal;
private readonly claimsSignal;
private readonly statusSignal;
readonly token: _angular_core.Signal<string | null>;
readonly claims: _angular_core.Signal<JwtClaims | null>;
readonly status: _angular_core.Signal<SessionStatus>;
readonly isAuthenticated: _angular_core.Signal<boolean>;
readonly role: _angular_core.Signal<_marketplaces_auth.AdminRole | null>;
private refreshTimer?;
private refreshCallback?;
/** Called once by AuthService on init to wire up the refresh trigger without a circular DI dependency. */
onRefreshDue(callback: () => void): void;
/** Restores session state from persisted storage. Returns true if a (possibly expired) session was found. */
restore(): boolean;
activate(tokens: AuthTokenPair): void;
getRefreshToken(): string | null;
markExpired(): void;
clear(): void;
private scheduleRefresh;
private clearRefreshTimer;
private readStorage;
private writeStorage;
private removeStorage;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<SessionService>;
}
/**
* Client-side JWT *decoding* only - never verification. The signature is
* meaningless to check here because the frontend has no trusted key to check
* it against; verifying a JWT's signature is the backend's job on every
* request. This service exists purely so the UI can read `role`/`exp` for
* display and route-gating UX (e.g. "session expires in 4m").
*/
declare class JwtService {
decode(token: string): JwtClaims | null;
isExpired(claims: JwtClaims, skewSeconds?: number): boolean;
private isJwtClaims;
private base64UrlDecode;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<JwtService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<JwtService>;
}
declare class Ed25519KeypairService {
private cached;
isSupported(): boolean;
/** Returns the device's Ed25519 keypair, generating and persisting one on first use. */
getOrCreateKeyPair(): Promise<{
publicKeyBase64: string;
}>;
sign(message: string): Promise<string>;
/** Discards the local keypair (e.g. "forget this device"). A new keypair on next login requires re-registration with the backend. */
clear(): Promise<void>;
private generateAndPersist;
private loadFromStore;
private openDatabase;
private toBase64;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Ed25519KeypairService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<Ed25519KeypairService>;
}
/**
* Derives the current admin's permission set from their JWT `role` claim.
* UI-only gate (hide/disable) - the backend must independently enforce
* every mutation server-side.
*/
declare class PermissionService {
private readonly session;
readonly permissions: _angular_core.Signal<readonly Permission[]>;
has(permission: Permission): boolean;
hasAny(permissions: readonly Permission[]): boolean;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<PermissionService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<PermissionService>;
}
/**
* Prep interfaces for a future Ed25519 challenge/response admin auth flow.
* No crypto is implemented here - verification is delegated to an injectable
* service so the real implementation (native WebCrypto Ed25519 support, or a
* backend verification call) can be swapped in once the backend API exists,
* without touching AdminAuthService or components.
*/
interface Ed25519Challenge {
nonce: string;
timestamp: string;
/** Opaque challenge payload the client must sign with its private key. */
payload: string;
}
interface Ed25519SignedResponse {
challenge: Ed25519Challenge;
publicKey: string;
signature: string;
}
interface Ed25519VerificationResult {
valid: boolean;
reason?: string;
}
declare abstract class Ed25519VerificationService {
abstract requestChallenge(): Observable<Ed25519Challenge>;
abstract verify(response: Ed25519SignedResponse): Observable<Ed25519VerificationResult>;
}
/**
* Default DI binding for Ed25519VerificationService until the backend ships
* the real challenge/verify endpoints. Intentionally fails closed (throws)
* rather than pretending to verify anything, so accidental use in a login
* path is loud instead of silently accepting unsigned sessions.
*/
declare class NoopEd25519VerificationService implements Ed25519VerificationService {
requestChallenge(): Observable<Ed25519Challenge>;
verify(_response: Ed25519SignedResponse): Observable<Ed25519VerificationResult>;
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NoopEd25519VerificationService, never>;
static ɵprov: _angular_core.ɵɵInjectableDeclaration<NoopEd25519VerificationService>;
}
export { AUTH_API_URL, AdminAuthService, AuthApiService, AuthFacade, AuthMarketplaceContext, AuthService$1 as AuthService, AuthService as Ed25519AuthService, Ed25519KeypairService, Ed25519VerificationService, HttpMarketplacesAuthGateway, JwtService, MARKETPLACES_AUTH_CONFIG, MARKETPLACES_AUTH_GATEWAY, MARKETPLACE_DOMAIN_HEADER, MarketplacesAuthComponent, NoopEd25519VerificationService, PermissionService, ROLE_PERMISSIONS, SessionService, TELEGRAM_BOT_USERNAME, TelegramSessionApiService, adminAuthGuard, adminAuthHeadersInterceptor, authErrorCodeFromBackendCode, authErrorCodeFromStatus, normalizeMarketplaceDomain, provideMarketplacesAuth };
export type { AdminAuthStatus, AdminRole, AuthChallenge, AuthError, AuthErrorCode, AuthFailure, AuthMethod, AuthMode, AuthResult, AuthSession, AuthStatus, AuthTokenPair, CredentialLogin, Ed25519Challenge, Ed25519SignedResponse, Ed25519VerificationResult, ExternalAuthStart, JwtClaims, LoginPhase, MarketplacesAuthConfig, MarketplacesAuthGateway, Permission, RefreshTokenRequest, SessionStatus, VerifySignatureRequest, WebSessionStart };