2026-08-21 08:10:12 +04:00
import * as i0 from '@angular/core' ;
import { InjectionToken , makeEnvironmentProviders , inject , Injectable , signal , computed , isDevMode , input , booleanAttribute , output , DestroyRef , Component } from '@angular/core' ;
import { HttpHeaders , HttpClient , HttpErrorResponse } from '@angular/common/http' ;
import { form , required , FormField } from '@angular/forms/signals' ;
import * as QRCode from 'qrcode' ;
import { map , of , catchError , tap , throwError , timer , switchMap , Observable , finalize } from 'rxjs' ;
import { Router } from '@angular/router' ;
/** Base URL for the auth backend, e.g. `https://api.example.com`. Provide from the consuming app's environment config. */
const AUTH _API _URL = new InjectionToken ( '@marketplaces/auth AUTH_API_URL' ) ;
/** Telegram bot username used to build QR/deep-link login URLs. Optional — falls back to a default if not provided. */
const TELEGRAM _BOT _USERNAME = new InjectionToken ( '@marketplaces/auth TELEGRAM_BOT_USERNAME' ) ;
const MARKETPLACES _AUTH _CONFIG = new InjectionToken ( '@marketplaces/auth config' ) ;
function provideMarketplacesAuth ( config ) {
const normalized = {
... config ,
apiUrl : config . apiUrl . replace ( /\/$/ , '' ) ,
credentialsPath : config . credentialsPath ? ? '/auth/credentials/login' ,
yandexStartPath : config . yandexStartPath ? ? '/auth/yandex/sessions' ,
yandexSessionPath : config . yandexSessionPath ? ? '/auth/yandex/sessions' ,
pollIntervalMs : config . pollIntervalMs ? ? 1500 ,
} ;
return makeEnvironmentProviders ( [
{ provide : MARKETPLACES _AUTH _CONFIG , useValue : normalized } ,
{ provide : AUTH _API _URL , useValue : normalized . apiUrl } ,
... ( normalized . telegramBotUsername
? [ { provide : TELEGRAM _BOT _USERNAME , useValue : normalized . telegramBotUsername } ]
: [ ] ) ,
] ) ;
}
const MARKETPLACE _DOMAIN _HEADER = 'X-Marketplace-Domain' ;
function normalizeMarketplaceDomain ( domain ) {
return domain . trim ( ) . toLowerCase ( ) . replace ( /\.$/ , '' ) ;
}
class AuthMarketplaceContext {
constructor ( ) {
2026-08-21 08:16:02 +04:00
this . config = inject ( MARKETPLACES _AUTH _CONFIG , { optional : true } ) ;
2026-08-21 08:10:12 +04:00
}
domain ( ) {
2026-08-21 08:16:02 +04:00
const configured = this . config ? . marketplaceDomain ;
2026-08-21 08:10:12 +04:00
const domain = typeof configured === 'function'
? configured ( )
: configured ? ? ( typeof location === 'undefined' ? '' : location . hostname ) ;
return normalizeMarketplaceDomain ( domain ) ;
}
headers ( extra ) {
const domain = this . domain ( ) ;
if ( ! domain )
throw new Error ( 'Marketplace domain cannot be resolved' ) ;
return new HttpHeaders ( { [ MARKETPLACE _DOMAIN _HEADER ] : domain , ... extra } ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthMarketplaceContext , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthMarketplaceContext , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthMarketplaceContext , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/** RFC4122 v4-ish GUID, using crypto when available. Shared by customer and admin session creation. */
function generateGuid ( ) {
if ( globalThis . crypto ? . randomUUID ) {
return globalThis . crypto . randomUUID ( ) ;
}
const bytes = new Uint8Array ( 16 ) ;
if ( globalThis . crypto ? . getRandomValues ) {
globalThis . crypto . getRandomValues ( bytes ) ;
}
else {
for ( let index = 0 ; index < bytes . length ; index ++ ) {
bytes [ index ] = Math . floor ( Math . random ( ) * 256 ) ;
}
}
bytes [ 6 ] = ( bytes [ 6 ] & 0x0f ) | 0x40 ;
bytes [ 8 ] = ( bytes [ 8 ] & 0x3f ) | 0x80 ;
const hex = Array . from ( bytes , byte => byte . toString ( 16 ) . padStart ( 2 , '0' ) ) ;
return ` ${ hex . slice ( 0 , 4 ) . join ( '' ) } - ${ hex . slice ( 4 , 6 ) . join ( '' ) } - ${ hex . slice ( 6 , 8 ) . join ( '' ) } - ${ hex . slice ( 8 , 10 ) . join ( '' ) } - ${ hex . slice ( 10 , 16 ) . join ( '' ) } ` ;
}
const SESSION _MAX _AGE _SECONDS = 60 * 60 ;
const DEFAULT _TELEGRAM _BOT _USERNAME = 'DexarSupport_bot' ;
/ * *
* 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 .
* /
class TelegramSessionApiService {
constructor ( ) {
this . http = inject ( HttpClient ) ;
this . authApiUrl = inject ( AUTH _API _URL ) ;
this . telegramBotUsername = inject ( TELEGRAM _BOT _USERNAME , { optional : true } ) ;
this . marketplaceContext = inject ( AuthMarketplaceContext ) ;
}
createSession ( ) {
const webSessionID = generateGuid ( ) ;
return this . http . post ( ` ${ this . authApiUrl } /users/sessions ` , { webSessionID } , { headers : this . marketplaceContext . headers ( { WebSessionID : webSessionID } ) } ) . pipe ( map ( response => {
const responseWebSessionID = this . extractSessionId ( response , webSessionID ) ;
return {
webSessionID : responseWebSessionID ,
url : this . getBotLoginUrl ( responseWebSessionID ) ,
} ;
} ) ) ;
}
checkSessionOnce ( webSessionID ) {
if ( ! webSessionID ) {
return of ( null ) ;
}
return this . http . get ( ` ${ this . authApiUrl } /users/sessions/ ${ encodeURIComponent ( webSessionID ) } ` , { headers : this . marketplaceContext . headers ( ) } ) . pipe ( map ( response => this . normalizeWebSession ( response , webSessionID ) ) , catchError ( ( ) => of ( null ) ) ) ;
}
logout ( webSessionID ) {
return this . http . delete ( ` ${ this . authApiUrl } /users/sessions/ ${ encodeURIComponent ( webSessionID ) } ` , {
headers : this . marketplaceContext . headers ( { WebSessionID : webSessionID } )
} ) . pipe ( catchError ( ( ) => of ( null ) ) ) ;
}
getBotLoginUrl ( webSessionID ) {
return ` https://t.me/ ${ this . getBotUsername ( ) } ?start= ${ encodeURIComponent ( webSessionID ) } ` ;
}
getBotAppLoginUrl ( webSessionID ) {
return ` tg://resolve?domain= ${ encodeURIComponent ( this . getBotUsername ( ) ) } &start= ${ encodeURIComponent ( webSessionID ) } ` ;
}
getBotUsername ( ) {
return this . telegramBotUsername || DEFAULT _TELEGRAM _BOT _USERNAME ;
}
normalizeWebSession ( response , fallbackSessionId ) {
if ( ! response ) {
return null ;
}
const user = this . asRecord ( this . readFirst ( response , [ 'user' , 'User' , 'telegramUser' , 'TelegramUser' ] ) ) ? ? response ;
const status = this . readFirst ( response , [
'status' , 'Status' , 'active' , 'Active' , 'loggedIn' , 'LoggedIn' ,
'isLoggedIn' , 'IsLoggedIn' , 'authenticated' , 'Authenticated'
] ) ;
const active = this . isActiveStatus ( status ) ;
const sessionId = this . extractSessionId ( response , fallbackSessionId ) ;
const username = this . readString ( this . readFirst ( user , [ 'username' , 'Username' ] ) )
? ? this . readString ( this . readFirst ( response , [ 'username' , 'Username' ] ) ) ;
const firstName = this . readString ( this . readFirst ( user , [ 'firstName' , 'first_name' , 'FirstName' , 'First_name' ] ) ) ;
const lastName = this . readString ( this . readFirst ( user , [ 'lastName' , 'last_name' , 'LastName' , 'Last_name' ] ) ) ;
const fullName = [ firstName , lastName ] . filter ( Boolean ) . join ( ' ' ) ;
const explicitDisplayName = this . readString ( this . readFirst ( response , [ 'displayName' , 'DisplayName' , 'name' , 'Name' ] ) )
? ? this . readString ( this . readFirst ( user , [ 'displayName' , 'DisplayName' , 'name' , 'Name' ] ) ) ;
const displayName = explicitDisplayName ? ? username ? ? ( fullName || 'Telegram User' ) ;
const telegramUserId = this . readNumber ( this . readFirst ( user , [ 'userId' , 'telegramUserId' , 'telegramUserID' , 'TelegramUserID' , 'id' , 'ID' ] ) )
? ? this . readNumber ( this . readFirst ( response , [ 'userId' , 'telegramUserId' , 'telegramUserID' , 'TelegramUserID' , 'userID' , 'UserID' , 'UserId' ] ) )
? ? null ;
const expiresAt = this . readString ( this . readFirst ( response , [ 'expiresAt' , 'ExpiresAt' , 'expires' , 'Expires' ] ) )
? ? new Date ( Date . now ( ) + SESSION _MAX _AGE _SECONDS * 1000 ) . toISOString ( ) ;
return { sessionId , userId : telegramUserId , username , displayName , active , expires : expiresAt } ;
}
extractSessionId ( response , fallbackSessionId ) {
if ( ! response ) {
return fallbackSessionId ;
}
return this . readString ( this . readFirst ( response , [
'webSessionID' , 'WebSessionID' , 'webSessionId' , 'sessionID' , 'SessionID' , 'sessionId' , 'id' , 'ID'
] ) ) ? ? fallbackSessionId ;
}
readFirst ( source , keys ) {
for ( const key of keys ) {
if ( Object . prototype . hasOwnProperty . call ( source , key ) ) {
return source [ key ] ;
}
}
return undefined ;
}
readString ( value ) {
if ( typeof value === 'string' && value . trim ( ) ) {
return value ;
}
if ( typeof value === 'number' || typeof value === 'bigint' ) {
return value . toString ( ) ;
}
return null ;
}
readNumber ( value ) {
if ( typeof value === 'number' && Number . isFinite ( value ) ) {
return value ;
}
if ( typeof value === 'string' ) {
const parsed = Number ( value ) ;
return Number . isFinite ( parsed ) ? parsed : null ;
}
return null ;
}
asRecord ( value ) {
return value !== null && typeof value === 'object' && ! Array . isArray ( value )
? value
: null ;
}
isActiveStatus ( status ) {
if ( status === true || status === 1 ) {
return true ;
}
if ( typeof status !== 'string' ) {
return false ;
}
return [ 'true' , '1' , 'active' , 'authenticated' , 'confirmed' , 'success' , 'logged_in' ] . includes ( status . toLowerCase ( ) ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : TelegramSessionApiService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : TelegramSessionApiService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : TelegramSessionApiService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/ * *
* Admin login uses the exact same Telegram QR / session API as the customer
* login ( TelegramSessionApiService ) - there is no separate admin backend
* endpoint , and none should be invented client - side . Only the * storage * is
* kept separate from AuthService , so an admin QR scan never authenticates
* the customer session or vice versa : distinct cookie name , distinct
* signals , distinct guard / interceptor .
*
* Since the session API itself has no concept of "admin" , the frontend
* cannot tell an admin Telegram session from a regular one . Actual admin
* authorization must be enforced server - side when admin API calls are made
* with the resulting session id - the frontend only decides where to
* * store * the result .
* /
const ADMIN _SESSION _COOKIE = 'adminSessionID' ;
const ADMIN _TOKEN _STORAGE _KEY = 'adminToken' ;
const ADMIN _REFRESH _STORAGE _KEY = 'adminRefreshToken' ;
const ADMIN _SESSION _COOKIE _MAX _AGE _SECONDS = 60 * 60 ;
class AdminAuthService {
constructor ( ) {
this . api = inject ( TelegramSessionApiService ) ;
this . sessionSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "sessionSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . statusSignal = signal ( 'unknown' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "statusSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . showLoginSignal = signal ( false , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "showLoginSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . session = this . sessionSignal . asReadonly ( ) ;
this . status = this . statusSignal . asReadonly ( ) ;
this . isAuthenticated = computed ( ( ) => this . statusSignal ( ) === 'authenticated' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "isAuthenticated" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . showLoginDialog = this . showLoginSignal . asReadonly ( ) ;
this . displayName = computed ( ( ) => this . sessionSignal ( ) ? . displayName ? ? null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "displayName" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . checkSession ( ) ;
}
checkSession ( ) {
const webSessionID = this . getStoredAdminSessionID ( ) ;
if ( ! webSessionID ) {
this . clearAuthState ( 'unauthenticated' ) ;
return ;
}
this . statusSignal . set ( 'checking' ) ;
this . checkSessionOnce ( webSessionID ) . subscribe ( session => {
if ( ! session ? . active ) {
this . clearAuthState ( 'unauthenticated' ) ;
}
} ) ;
}
/** Check session without mutating internal state beyond activating on success (used for polling). */
checkSessionOnce ( webSessionID = this . getStoredAdminSessionID ( ) ) {
return this . api . checkSessionOnce ( webSessionID ) . pipe ( tap ( session => {
if ( session ? . active ) {
this . activateSession ( session ) ;
}
} ) ) ;
}
/** Create a backend web session - identical call to the customer login (TelegramSessionApiService.createSession). */
createWebSession ( ) {
return this . api . createSession ( ) ;
}
getAdminAppLoginUrl ( webSessionID ) {
return this . api . getBotAppLoginUrl ( webSessionID ) ;
}
onLoginComplete ( ) {
this . hideLogin ( ) ;
if ( ! this . isAuthenticated ( ) ) {
this . checkSession ( ) ;
}
}
requestLogin ( ) {
this . showLoginSignal . set ( true ) ;
}
/ * *
* 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 ( ) {
if ( ! isDevMode ( ) ) {
return ;
}
this . hideLogin ( ) ;
this . activateSession ( {
sessionId : ` dev-bypass- ${ Date . now ( ) } ` ,
userId : 0 ,
username : 'dev-admin' ,
displayName : 'Dev Admin (local bypass)' ,
active : true ,
expires : new Date ( Date . now ( ) + 60 * 60 * 1000 ) . toISOString ( ) ,
} ) ;
}
hideLogin ( ) {
this . showLoginSignal . set ( false ) ;
}
logout ( ) {
const webSessionID = this . sessionSignal ( ) ? . sessionId || this . getStoredAdminSessionID ( ) ;
if ( ! webSessionID ) {
this . clearAuthState ( 'unauthenticated' ) ;
return ;
}
this . api . logout ( webSessionID ) . subscribe ( ( ) => this . clearAuthState ( 'unauthenticated' ) ) ;
}
/** Accept a session/tokens returned by credentials or an external provider. */
acceptSession ( session , token , refreshToken ) {
this . activateSession ( session ) ;
if ( token && refreshToken )
this . setAdminTokens ( token , refreshToken ) ;
}
/** JWT pair storage, reserved for once the backend issues admin access/refresh tokens. Unused until then. */
getAdminToken ( ) {
return typeof localStorage === 'undefined' ? null : localStorage . getItem ( ADMIN _TOKEN _STORAGE _KEY ) ;
}
setAdminTokens ( token , refreshToken ) {
if ( typeof localStorage === 'undefined' ) {
return ;
}
localStorage . setItem ( ADMIN _TOKEN _STORAGE _KEY , token ) ;
localStorage . setItem ( ADMIN _REFRESH _STORAGE _KEY , refreshToken ) ;
}
clearAdminTokens ( ) {
if ( typeof localStorage === 'undefined' ) {
return ;
}
localStorage . removeItem ( ADMIN _TOKEN _STORAGE _KEY ) ;
localStorage . removeItem ( ADMIN _REFRESH _STORAGE _KEY ) ;
}
activateSession ( session ) {
this . sessionSignal . set ( session ) ;
this . statusSignal . set ( 'authenticated' ) ;
this . setStoredAdminSessionID ( session . sessionId ) ;
this . scheduleSessionRefresh ( session . expires ) ;
}
clearAuthState ( status ) {
this . sessionSignal . set ( null ) ;
this . statusSignal . set ( status ) ;
this . clearStoredAdminSessionID ( ) ;
this . clearAdminTokens ( ) ;
this . clearSessionRefresh ( ) ;
}
scheduleSessionRefresh ( expiresAt ) {
this . clearSessionRefresh ( ) ;
const expiresMs = new Date ( expiresAt ) . getTime ( ) ;
const nowMs = Date . now ( ) ;
const refreshIn = Number . isFinite ( expiresMs )
? Math . max ( expiresMs - nowMs - 60_000 , 30_000 )
: ADMIN _SESSION _COOKIE _MAX _AGE _SECONDS * 1000 ;
this . sessionCheckTimer = setTimeout ( ( ) => this . checkSession ( ) , refreshIn ) ;
}
clearSessionRefresh ( ) {
if ( this . sessionCheckTimer ) {
clearTimeout ( this . sessionCheckTimer ) ;
this . sessionCheckTimer = undefined ;
}
}
getStoredAdminSessionID ( ) {
if ( typeof document === 'undefined' ) {
return null ;
}
const cookie = document . cookie . split ( '; ' ) . find ( row => row . startsWith ( ` ${ ADMIN _SESSION _COOKIE } = ` ) ) ;
if ( ! cookie ) {
return null ;
}
try {
return decodeURIComponent ( cookie . substring ( ADMIN _SESSION _COOKIE . length + 1 ) ) ;
}
catch {
return null ;
}
}
setStoredAdminSessionID ( webSessionID ) {
if ( typeof document === 'undefined' ) {
return ;
}
const secure = typeof window !== 'undefined' && window . location . protocol === 'https:' ? '; Secure' : '' ;
document . cookie = ` ${ ADMIN _SESSION _COOKIE } = ${ encodeURIComponent ( webSessionID ) } ; Max-Age= ${ ADMIN _SESSION _COOKIE _MAX _AGE _SECONDS } ; Path=/; SameSite=Strict ${ secure } ` ;
}
clearStoredAdminSessionID ( ) {
if ( typeof document === 'undefined' ) {
return ;
}
document . cookie = ` ${ ADMIN _SESSION _COOKIE } =; Max-Age=0; Path=/; SameSite=Strict ` ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AdminAuthService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AdminAuthService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AdminAuthService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] , ctorParameters : ( ) => [ ] } ) ;
const WEB _SESSION _COOKIE = 'webSessionID' ;
const WEB _SESSION _COOKIE _MAX _AGE _SECONDS = 60 * 60 ;
/** Customer-facing Telegram QR/session auth. Distinct storage/state from AdminAuthService by design. */
let AuthService$1 = class AuthService {
constructor ( ) {
this . api = inject ( TelegramSessionApiService ) ;
this . sessionSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "sessionSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . statusSignal = signal ( 'unknown' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "statusSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . showLoginSignal = signal ( false , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "showLoginSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
/** Current auth session */
this . session = this . sessionSignal . asReadonly ( ) ;
/** Current auth status */
this . status = this . statusSignal . asReadonly ( ) ;
/** Whether user is fully authenticated */
this . isAuthenticated = computed ( ( ) => this . statusSignal ( ) === 'authenticated' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "isAuthenticated" } ] : /* istanbul ignore next */ [ ] ) ) ;
/** Whether to show login dialog */
this . showLoginDialog = this . showLoginSignal . asReadonly ( ) ;
/** Display name of authenticated user */
this . displayName = computed ( ( ) => this . sessionSignal ( ) ? . displayName ? ? null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "displayName" } ] : /* istanbul ignore next */ [ ] ) ) ;
// On init, check existing session via cookie
this . checkSession ( ) ;
}
/** Check the current webSessionID cookie against the auth backend. */
checkSession ( ) {
const webSessionID = this . getStoredWebSessionID ( ) ;
if ( ! webSessionID ) {
this . clearAuthState ( 'unauthenticated' ) ;
return ;
}
this . statusSignal . set ( 'checking' ) ;
this . checkSessionOnce ( webSessionID ) . subscribe ( session => {
if ( ! session ? . active ) {
this . clearAuthState ( 'unauthenticated' ) ;
}
} ) ;
}
/** Check session without updating internal state beyond activating on success (used for polling). */
checkSessionOnce ( webSessionID = this . getStoredWebSessionID ( ) ) {
return this . api . checkSessionOnce ( webSessionID ) . pipe ( tap ( session => {
if ( session ? . active ) {
this . activateSession ( session ) ;
}
} ) ) ;
}
/ * *
* Called after user completes Telegram login .
* /
onTelegramLoginComplete ( ) {
this . hideLogin ( ) ;
if ( ! this . isAuthenticated ( ) ) {
this . checkSession ( ) ;
}
}
/** Generate the Telegram login URL for bot-based auth */
getTelegramLoginUrl ( webSessionID ) {
return this . api . getBotLoginUrl ( webSessionID ) ;
}
/** Generate a Telegram app deep link for mobile login without opening a browser tab. */
getTelegramAppLoginUrl ( webSessionID ) {
return this . api . getBotAppLoginUrl ( webSessionID ) ;
}
/** Create a backend web session and return the Telegram start link for it. */
createWebSession ( ) {
return this . api . createSession ( ) ;
}
/** Show login dialog (called when user tries to pay without being logged in) */
requestLogin ( ) {
this . showLoginSignal . set ( true ) ;
}
/** Hide login dialog */
hideLogin ( ) {
this . showLoginSignal . set ( false ) ;
}
/** Logout — clears session on backend and locally */
logout ( ) {
const webSessionID = this . sessionSignal ( ) ? . sessionId || this . getStoredWebSessionID ( ) ;
if ( ! webSessionID ) {
this . clearAuthState ( 'unauthenticated' ) ;
return ;
}
this . api . logout ( webSessionID ) . subscribe ( ( ) => {
this . clearAuthState ( 'unauthenticated' ) ;
} ) ;
}
/** Accept a session returned by credentials or an external provider. */
acceptSession ( session ) {
this . activateSession ( session ) ;
}
activateSession ( session ) {
this . sessionSignal . set ( session ) ;
this . statusSignal . set ( 'authenticated' ) ;
this . setStoredWebSessionID ( session . sessionId ) ;
this . scheduleSessionRefresh ( session . expires ) ;
}
clearAuthState ( status ) {
this . sessionSignal . set ( null ) ;
this . statusSignal . set ( status ) ;
this . clearStoredWebSessionID ( ) ;
this . clearSessionRefresh ( ) ;
}
/** Schedule a session re-check before it expires */
scheduleSessionRefresh ( expiresAt ) {
this . clearSessionRefresh ( ) ;
const expiresMs = new Date ( expiresAt ) . getTime ( ) ;
const nowMs = Date . now ( ) ;
// Re-check 60 seconds before expiry, minimum 30s from now
const refreshIn = Number . isFinite ( expiresMs )
? Math . max ( expiresMs - nowMs - 60_000 , 30_000 )
: WEB _SESSION _COOKIE _MAX _AGE _SECONDS * 1000 ;
this . sessionCheckTimer = setTimeout ( ( ) => {
this . checkSession ( ) ;
} , refreshIn ) ;
}
clearSessionRefresh ( ) {
if ( this . sessionCheckTimer ) {
clearTimeout ( this . sessionCheckTimer ) ;
this . sessionCheckTimer = undefined ;
}
}
getStoredWebSessionID ( ) {
if ( typeof document === 'undefined' ) {
return null ;
}
const cookie = document . cookie
. split ( '; ' )
. find ( row => row . startsWith ( ` ${ WEB _SESSION _COOKIE } = ` ) ) ;
if ( ! cookie ) {
return null ;
}
try {
return decodeURIComponent ( cookie . substring ( WEB _SESSION _COOKIE . length + 1 ) ) ;
}
catch {
return null ;
}
}
setStoredWebSessionID ( webSessionID ) {
if ( typeof document === 'undefined' ) {
return ;
}
const secure = typeof window !== 'undefined' && window . location . protocol === 'https:' ? '; Secure' : '' ;
document . cookie = ` ${ WEB _SESSION _COOKIE } = ${ encodeURIComponent ( webSessionID ) } ; Max-Age= ${ WEB _SESSION _COOKIE _MAX _AGE _SECONDS } ; Path=/; SameSite=Lax ${ secure } ` ;
}
clearStoredWebSessionID ( ) {
if ( typeof document === 'undefined' ) {
return ;
}
document . cookie = ` ${ WEB _SESSION _COOKIE } =; Max-Age=0; Path=/; SameSite=Lax ` ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService , providedIn : 'root' } ) ; }
} ;
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService$1 , decorators : [ {
type : Injectable ,
args : [ {
providedIn : 'root'
} ]
} ] , ctorParameters : ( ) => [ ] } ) ;
const MARKETPLACES _AUTH _GATEWAY = new InjectionToken ( '@marketplaces/auth gateway' , { providedIn : 'root' , factory : ( ) => inject ( HttpMarketplacesAuthGateway ) } ) ;
class HttpMarketplacesAuthGateway {
constructor ( ) {
this . http = inject ( HttpClient ) ;
this . config = inject ( MARKETPLACES _AUTH _CONFIG ) ;
this . context = inject ( AuthMarketplaceContext ) ;
this . customerAuth = inject ( AuthService$1 ) ;
this . adminAuth = inject ( AdminAuthService ) ;
}
startQr ( mode ) {
return mode === 'admin' ? this . adminAuth . createWebSession ( ) : this . customerAuth . createWebSession ( ) ;
}
checkQr ( mode , attemptId ) {
return mode === 'admin' ? this . adminAuth . checkSessionOnce ( attemptId ) : this . customerAuth . checkSessionOnce ( attemptId ) ;
}
loginWithCredentials ( mode , credentials ) {
return this . http . post ( this . url ( this . config . credentialsPath ) , { ... credentials , mode } , {
headers : this . context . headers ( ) ,
} ) . pipe ( map ( result => this . accept ( mode , { ... result , method : 'credentials' , mode } ) ) , catchError ( error => throwError ( ( ) => this . failure ( 'credentials' , error ) ) ) ) ;
}
startYandex ( mode , returnUrl ) {
return this . http . post ( this . url ( this . config . yandexStartPath ) , {
provider : 'yandex' , mode , returnUrl ,
} , { headers : this . context . headers ( ) } ) . pipe ( catchError ( error => throwError ( ( ) => this . failure ( 'yandex' , error ) ) ) ) ;
}
checkYandex ( mode , attemptId ) {
return this . http . get ( ` ${ this . url ( this . config . yandexSessionPath ) } / ${ encodeURIComponent ( attemptId ) } ` , { headers : this . context . headers ( ) } ) . pipe ( map ( result => result ? this . accept ( mode , { ... result , method : 'yandex' , mode } ) : null ) , catchError ( ( error ) => error . status === 404 || error . status === 202
? of ( null )
: throwError ( ( ) => this . failure ( 'yandex' , error ) ) ) ) ;
}
accept ( mode , result ) {
if ( mode === 'admin' )
this . adminAuth . acceptSession ( result . session , result . accessToken , result . refreshToken ) ;
else
this . customerAuth . acceptSession ( result . session ) ;
return result ;
}
url ( path = '' ) { return ` ${ this . config . apiUrl } ${ path . startsWith ( '/' ) ? path : ` / ${ path } ` } ` ; }
failure ( method , cause ) {
const response = cause instanceof HttpErrorResponse ? cause : null ;
return {
method ,
code : response ? . status === 401 ? 'invalid_credentials' : 'backend' ,
message : response ? . error ? . message || response ? . message || 'Authentication failed' ,
cause ,
} ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : HttpMarketplacesAuthGateway , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : HttpMarketplacesAuthGateway , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : HttpMarketplacesAuthGateway , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
class MarketplacesAuthComponent {
constructor ( ) {
this . qr = input ( false , { ... ( ngDevMode ? { debugName : "qr" } : /* istanbul ignore next */ { } ) , transform : booleanAttribute } ) ;
this . credentials = input ( false , { ... ( ngDevMode ? { debugName : "credentials" } : /* istanbul ignore next */ { } ) , transform : booleanAttribute } ) ;
this . yandex = input ( false , { ... ( ngDevMode ? { debugName : "yandex" } : /* istanbul ignore next */ { } ) , transform : booleanAttribute } ) ;
this . mode = input ( 'customer' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "mode" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . title = input ( 'Вход' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "title" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . authenticated = output ( ) ;
this . authError = output ( ) ;
this . cancelled = output ( ) ;
this . method = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "method" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . busy = signal ( false , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "busy" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . error = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "error" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . qrImage = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "qrImage" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . externalUrl = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "externalUrl" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . credentialsModel = signal ( { login : '' , password : '' } , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "credentialsModel" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . credentialsForm = form ( this . credentialsModel , path => {
required ( path . login , { message : 'Введите логин' } ) ;
required ( path . password , { message : 'Введите пароль' } ) ;
} ) ;
this . gateway = inject ( MARKETPLACES _AUTH _GATEWAY ) ;
this . config = inject ( MARKETPLACES _AUTH _CONFIG ) ;
inject ( DestroyRef ) . onDestroy ( ( ) => this . poll ? . unsubscribe ( ) ) ;
queueMicrotask ( ( ) => this . select ( this . qr ( ) ? 'qr' : this . credentials ( ) ? 'credentials' : this . yandex ( ) ? 'yandex' : null ) ) ;
}
select ( method ) { this . poll ? . unsubscribe ( ) ; this . busy . set ( false ) ; this . error . set ( null ) ; this . method . set ( method ) ; }
startQr ( ) {
this . begin ( ) ;
this . gateway . startQr ( this . mode ( ) ) . subscribe ( {
next : attempt => void this . prepareQr ( attempt . url , attempt . webSessionID ) . catch ( cause => this . fail ( 'qr' , cause ) ) ,
error : cause => this . fail ( 'qr' , cause ) ,
} ) ;
}
loginWithCredentials ( event ) {
event . preventDefault ( ) ;
if ( this . credentialsForm ( ) . invalid ( ) )
return ;
this . begin ( ) ;
this . gateway . loginWithCredentials ( this . mode ( ) , this . credentialsModel ( ) ) . subscribe ( {
next : result => this . finish ( result ) , error : cause => this . fail ( 'credentials' , cause ) ,
} ) ;
}
startYandex ( ) {
this . begin ( ) ;
const returnUrl = typeof location === 'undefined' ? '' : location . href ;
this . gateway . startYandex ( this . mode ( ) , returnUrl ) . subscribe ( {
next : attempt => {
const popup = typeof window === 'undefined' ? null : window . open ( attempt . authorizationUrl , 'mp-yandex-auth' , 'popup,width=520,height=720' ) ;
if ( ! popup ) {
this . fail ( 'yandex' , { method : 'yandex' , code : 'popup_blocked' , message : 'Браузер заблокировал окно Яндекса' } ) ;
return ;
}
this . pollForYandex ( attempt . attemptId ) ;
} ,
error : cause => this . fail ( 'yandex' , cause ) ,
} ) ;
}
pollForQr ( attemptId ) {
this . poll ? . unsubscribe ( ) ;
this . poll = timer ( 0 , this . config . pollIntervalMs ? ? 1500 ) . pipe ( switchMap ( ( ) => this . gateway . checkQr ( this . mode ( ) , attemptId ) ) )
. subscribe ( { next : session => { if ( session ? . active )
this . finish ( { method : 'qr' , mode : this . mode ( ) , session } ) ; } , error : cause => this . fail ( 'qr' , cause ) } ) ;
}
async prepareQr ( url , attemptId ) {
this . externalUrl . set ( url ) ;
this . qrImage . set ( await QRCode . toDataURL ( url , { width : 320 , margin : 1 } ) ) ;
this . pollForQr ( attemptId ) ;
}
pollForYandex ( attemptId ) {
this . poll ? . unsubscribe ( ) ;
this . poll = timer ( 0 , this . config . pollIntervalMs ? ? 1500 ) . pipe ( switchMap ( ( ) => this . gateway . checkYandex ( this . mode ( ) , attemptId ) ) )
. subscribe ( { next : result => { if ( result )
this . finish ( result ) ; } , error : cause => this . fail ( 'yandex' , cause ) } ) ;
}
begin ( ) { this . poll ? . unsubscribe ( ) ; this . error . set ( null ) ; this . busy . set ( true ) ; }
finish ( result ) { this . poll ? . unsubscribe ( ) ; this . busy . set ( false ) ; this . authenticated . emit ( result ) ; }
fail ( method , cause ) {
this . poll ? . unsubscribe ( ) ;
this . busy . set ( false ) ;
const failure = this . isFailure ( cause ) ? cause : { method , code : 'backend' , message : 'Н е удалось выполнить вход' , cause } ;
this . error . set ( failure ) ;
this . authError . emit ( failure ) ;
}
isFailure ( value ) { return ! ! value && typeof value === 'object' && 'code' in value && 'message' in value ; }
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : MarketplacesAuthComponent , deps : [ ] , target : i0 . ɵɵFactoryTarget . Component } ) ; }
static { this . ɵcmp = i0 . ɵɵngDeclareComponent ( { minVersion : "17.0.0" , version : "22.0.8" , type : MarketplacesAuthComponent , isStandalone : true , selector : "mp-auth, marketplaces-auth" , inputs : { qr : { classPropertyName : "qr" , publicName : "qr" , isSignal : true , isRequired : false , transformFunction : null } , credentials : { classPropertyName : "credentials" , publicName : "credentials" , isSignal : true , isRequired : false , transformFunction : null } , yandex : { classPropertyName : "yandex" , publicName : "yandex" , isSignal : true , isRequired : false , transformFunction : null } , mode : { classPropertyName : "mode" , publicName : "mode" , isSignal : true , isRequired : false , transformFunction : null } , title : { classPropertyName : "title" , publicName : "title" , isSignal : true , isRequired : false , transformFunction : null } } , outputs : { authenticated : "authenticated" , authError : "authError" , cancelled : "cancelled" } , ngImport : i0 , template : `
< section class = "mp-auth" aria - labelledby = "mp-auth-title" >
< h2 id = "mp-auth-title" > { { title ( ) } } < / h 2 >
< div class = "methods" role = "tablist" aria - label = "Способ входа" >
@ if ( qr ( ) ) { < button type = "button" [ class . active ] = "method() === 'qr'" ( click ) = "select('qr')" > QR < / b u t t o n > }
@ if ( credentials ( ) ) { < button type = "button" [ class . active ] = "method() === 'credentials'" ( click ) = "select('credentials')" > Логин < / b u t t o n > }
@ if ( yandex ( ) ) { < button type = "button" [ class . active ] = "method() === 'yandex'" ( click ) = "select('yandex')" > Яндекс < / b u t t o n > }
< / d i v >
@ if ( method ( ) === 'credentials' ) {
< form ( submit ) = "loginWithCredentials($event)" >
< label > Логин < input autocomplete = "username" [ formField ] = "credentialsForm.login" / > < / l a b e l >
< label > Пароль < input type = "password" autocomplete = "current-password" [ formField ] = "credentialsForm.password" / > < / l a b e l >
< button type = "submit" [ disabled ] = "busy() || credentialsForm().invalid()" > Войти < / b u t t o n >
< / f o r m >
}
@ if ( method ( ) === 'qr' ) {
@ if ( qrImage ( ) ) { < a [ href ] = "externalUrl()!" target = "_blank" rel = "noopener" > < img [ src ] = "qrImage()!" alt = "QR-код для входа" / > < / a > }
< button type = "button" [ disabled ] = "busy()" ( click ) = "startQr()" > { { qrImage ( ) ? 'Обновить QR' : 'Получить QR' } } < / b u t t o n >
}
@ if ( method ( ) === 'yandex' ) { < button type = "button" [ disabled ] = "busy()" ( click ) = "startYandex()" > Войти через Яндекс < / b u t t o n > }
@ if ( busy ( ) ) { < p role = "status" > Ожидаем подтверждение … < / p > }
@ if ( error ( ) ) { < p class = "error" role = "alert" > { { error ( ) ! . message } } < / p > }
< / s e c t i o n >
` , isInline: true, styles: [":host{display:block}.mp-auth{font:inherit;color:inherit;display:grid;gap:1rem;max-width:25rem}h2,p{margin:0}.methods{display:flex;gap:.5rem;flex-wrap:wrap}.methods button{background:transparent;color:inherit}button,input{font:inherit;border:1px solid #c7c7c7;border-radius:.65rem;padding:.7rem .9rem}button{cursor:pointer}.active,button[type=submit]{background:#111;color:#fff;border-color:#111}button:disabled{opacity:.55;cursor:wait}form{display:grid;gap:.8rem}label{display:grid;gap:.35rem}img{display:block;width:min(15rem,100%);height:auto;border-radius:.75rem}.error{color:#b42318} \n "], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }] }); }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : MarketplacesAuthComponent , decorators : [ {
type : Component ,
args : [ { selector : 'mp-auth, marketplaces-auth' , standalone : true , imports : [ FormField ] , template : `
< section class = "mp-auth" aria - labelledby = "mp-auth-title" >
< h2 id = "mp-auth-title" > { { title ( ) } } < / h 2 >
< div class = "methods" role = "tablist" aria - label = "Способ входа" >
@ if ( qr ( ) ) { < button type = "button" [ class . active ] = "method() === 'qr'" ( click ) = "select('qr')" > QR < / b u t t o n > }
@ if ( credentials ( ) ) { < button type = "button" [ class . active ] = "method() === 'credentials'" ( click ) = "select('credentials')" > Логин < / b u t t o n > }
@ if ( yandex ( ) ) { < button type = "button" [ class . active ] = "method() === 'yandex'" ( click ) = "select('yandex')" > Яндекс < / b u t t o n > }
< / d i v >
@ if ( method ( ) === 'credentials' ) {
< form ( submit ) = "loginWithCredentials($event)" >
< label > Логин < input autocomplete = "username" [ formField ] = "credentialsForm.login" / > < / l a b e l >
< label > Пароль < input type = "password" autocomplete = "current-password" [ formField ] = "credentialsForm.password" / > < / l a b e l >
< button type = "submit" [ disabled ] = "busy() || credentialsForm().invalid()" > Войти < / b u t t o n >
< / f o r m >
}
@ if ( method ( ) === 'qr' ) {
@ if ( qrImage ( ) ) { < a [ href ] = "externalUrl()!" target = "_blank" rel = "noopener" > < img [ src ] = "qrImage()!" alt = "QR-код для входа" / > < / a > }
< button type = "button" [ disabled ] = "busy()" ( click ) = "startQr()" > { { qrImage ( ) ? 'Обновить QR' : 'Получить QR' } } < / b u t t o n >
}
@ if ( method ( ) === 'yandex' ) { < button type = "button" [ disabled ] = "busy()" ( click ) = "startYandex()" > Войти через Яндекс < / b u t t o n > }
@ if ( busy ( ) ) { < p role = "status" > Ожидаем подтверждение … < / p > }
@ if ( error ( ) ) { < p class = "error" role = "alert" > { { error ( ) ! . message } } < / p > }
< / s e c t i o n >
` , styles: [":host{display:block}.mp-auth{font:inherit;color:inherit;display:grid;gap:1rem;max-width:25rem}h2,p{margin:0}.methods{display:flex;gap:.5rem;flex-wrap:wrap}.methods button{background:transparent;color:inherit}button,input{font:inherit;border:1px solid #c7c7c7;border-radius:.65rem;padding:.7rem .9rem}button{cursor:pointer}.active,button[type=submit]{background:#111;color:#fff;border-color:#111}button:disabled{opacity:.55;cursor:wait}form{display:grid;gap:.8rem}label{display:grid;gap:.35rem}img{display:block;width:min(15rem,100%);height:auto;border-radius:.75rem}.error{color:#b42318} \n "] }]
} ] , ctorParameters : ( ) => [ ] , propDecorators : { qr : [ { type : i0 . Input , args : [ { isSignal : true , alias : "qr" , required : false } ] } ] , credentials : [ { type : i0 . Input , args : [ { isSignal : true , alias : "credentials" , required : false } ] } ] , yandex : [ { type : i0 . Input , args : [ { isSignal : true , alias : "yandex" , required : false } ] } ] , mode : [ { type : i0 . Input , args : [ { isSignal : true , alias : "mode" , required : false } ] } ] , title : [ { type : i0 . Input , args : [ { isSignal : true , alias : "title" , required : false } ] } ] , authenticated : [ { type : i0 . Output , args : [ "authenticated" ] } ] , authError : [ { type : i0 . Output , args : [ "authError" ] } ] , cancelled : [ { type : i0 . Output , args : [ "cancelled" ] } ] } } ) ;
/** Guards `/admin/**`-style routes. Never shares state with the customer auth guard/service. */
const adminAuthGuard = ( ) => {
const adminAuth = inject ( AdminAuthService ) ;
if ( adminAuth . isAuthenticated ( ) ) {
return true ;
}
adminAuth . requestLogin ( ) ;
return false ;
} ;
/** Backend paths that require an active AdminWebSessionID. Adjust to match your API surface if consuming this outside marketplaces. */
const ADMIN _GATED _PATH _SEGMENTS = [ '/admin/' , '/backoffice/' , '/builder/' , '/media/' ] ;
/ * *
* 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 .
* /
const adminAuthHeadersInterceptor = ( req , next ) => {
const isAdminRequest = ADMIN _GATED _PATH _SEGMENTS . some ( segment => req . url . includes ( segment ) ) ;
if ( ! isAdminRequest ) {
return next ( req ) ;
}
const adminAuth = inject ( AdminAuthService ) ;
const session = adminAuth . session ( ) ;
const token = adminAuth . getAdminToken ( ) ;
let headers = req . headers ;
if ( session ? . sessionId ) {
headers = headers . set ( 'AdminWebSessionID' , session . sessionId ) ;
}
if ( token ) {
headers = headers . set ( 'Authorization' , ` Bearer ${ token } ` ) ;
}
return next ( req . clone ( { headers } ) ) ;
} ;
/** Maps a backend error envelope's `error.code` to the client's AuthErrorCode screens. Only codes with a dedicated screen are mapped; anything else falls back to the HTTP-status-derived code via authErrorCodeFromStatus. */
const BACKEND _ERROR _CODE _MAP = {
TOKEN _EXPIRED : 'session-expired' ,
INVALID _SIGNATURE : 'invalid-signature' ,
UNAUTHENTICATED : 'unauthorized' ,
FORBIDDEN : 'forbidden' ,
SERVICE _UNAVAILABLE : 'backend-unavailable' ,
} ;
function authErrorCodeFromBackendCode ( code ) {
return typeof code === 'string' ? BACKEND _ERROR _CODE _MAP [ code ] : undefined ;
}
/** Maps a backend HTTP status to the AuthErrorCode screen it should route to. */
function authErrorCodeFromStatus ( status ) {
switch ( status ) {
case 401 :
return 'unauthorized' ;
case 403 :
return 'forbidden' ;
case 0 :
return 'backend-unavailable' ;
default :
return status >= 500 ? 'backend-unavailable' : 'unauthorized' ;
}
}
/ * *
* Thin HTTP client for the Ed25519 admin auth endpoints . These endpoints may
* not exist on every backend yet - calling them before the backend ships
* 404 s 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 .
* /
class AuthApiService {
constructor ( ) {
this . http = inject ( HttpClient ) ;
this . baseUrl = ` ${ inject ( AUTH _API _URL ) } /api/admin/auth ` ;
}
requestChallenge ( ) {
return this . http . get ( ` ${ this . baseUrl } /challenge ` ) ;
}
verifySignature ( request ) {
return this . http . post ( ` ${ this . baseUrl } /verify ` , request ) ;
}
refresh ( request ) {
return this . http . post ( ` ${ this . baseUrl } /refresh ` , request ) ;
}
logout ( refreshToken ) {
return this . http . post ( ` ${ this . baseUrl } /logout ` , { refreshToken } ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthApiService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthApiService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthApiService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/ * *
* Manages the browser - local Ed25519 keypair used to sign admin auth
* challenges . Real WebCrypto Ed25519 ( RFC 8032 support landed in evergreen
* browsers ) - not a placeholder . The private key is generated
* non - extractable and kept only in IndexedDB as a CryptoKey handle ; it is
* never serialized , never sent anywhere , and cannot be exported by design .
*
* Registering ` publicKey ` with an admin ' s account ( associating it with a
* role ) is a backend - side , out - of - band operation ( e . g . an Owner approving a
* new admin 's public key) - entirely outside this frontend' s scope .
* /
const DB _NAME = 'admin-auth-ed25519' ;
const DB _VERSION = 1 ;
const STORE _NAME = 'keypair' ;
const KEY _RECORD _ID = 'device-keypair' ;
class Ed25519KeypairService {
constructor ( ) {
this . cached = null ;
}
isSupported ( ) {
return typeof crypto !== 'undefined' && ! ! crypto . subtle && typeof indexedDB !== 'undefined' ;
}
/** Returns the device's Ed25519 keypair, generating and persisting one on first use. */
async getOrCreateKeyPair ( ) {
if ( ! this . isSupported ( ) ) {
throw new Error ( 'Ed25519 is not supported in this browser (requires WebCrypto + IndexedDB).' ) ;
}
const existing = await this . loadFromStore ( ) ;
if ( existing ) {
this . cached = existing ;
return { publicKeyBase64 : existing . publicKeyBase64 } ;
}
const generated = await this . generateAndPersist ( ) ;
this . cached = generated ;
return { publicKeyBase64 : generated . publicKeyBase64 } ;
}
async sign ( message ) {
const keyPair = this . cached ? ? ( await this . loadFromStore ( ) ) ;
if ( ! keyPair ) {
throw new Error ( 'No Ed25519 keypair available - call getOrCreateKeyPair() first.' ) ;
}
const signatureBuffer = await crypto . subtle . sign ( 'Ed25519' , keyPair . privateKey , new TextEncoder ( ) . encode ( message ) ) ;
return this . toBase64 ( new Uint8Array ( signatureBuffer ) ) ;
}
/** Discards the local keypair (e.g. "forget this device"). A new keypair on next login requires re-registration with the backend. */
async clear ( ) {
this . cached = null ;
const db = await this . openDatabase ( ) ;
await new Promise ( ( resolve , reject ) => {
const tx = db . transaction ( STORE _NAME , 'readwrite' ) ;
tx . objectStore ( STORE _NAME ) . delete ( KEY _RECORD _ID ) ;
tx . oncomplete = ( ) => resolve ( ) ;
tx . onerror = ( ) => reject ( tx . error ) ;
} ) ;
}
async generateAndPersist ( ) {
const keyPair = ( await crypto . subtle . generateKey ( { name : 'Ed25519' } , false , [ 'sign' , 'verify' ] ) ) ;
const publicKeyRaw = await crypto . subtle . exportKey ( 'raw' , keyPair . publicKey ) ;
const publicKeyBase64 = this . toBase64 ( new Uint8Array ( publicKeyRaw ) ) ;
const record = {
id : KEY _RECORD _ID ,
publicKey : keyPair . publicKey ,
privateKey : keyPair . privateKey ,
publicKeyBase64
} ;
const db = await this . openDatabase ( ) ;
await new Promise ( ( resolve , reject ) => {
const tx = db . transaction ( STORE _NAME , 'readwrite' ) ;
tx . objectStore ( STORE _NAME ) . put ( record ) ;
tx . oncomplete = ( ) => resolve ( ) ;
tx . onerror = ( ) => reject ( tx . error ) ;
} ) ;
return record ;
}
async loadFromStore ( ) {
const db = await this . openDatabase ( ) ;
return new Promise ( ( resolve , reject ) => {
const tx = db . transaction ( STORE _NAME , 'readonly' ) ;
const request = tx . objectStore ( STORE _NAME ) . get ( KEY _RECORD _ID ) ;
request . onsuccess = ( ) => resolve ( request . result ? ? null ) ;
request . onerror = ( ) => reject ( request . error ) ;
} ) ;
}
openDatabase ( ) {
return new Promise ( ( resolve , reject ) => {
const request = indexedDB . open ( DB _NAME , DB _VERSION ) ;
request . onupgradeneeded = ( ) => {
if ( ! request . result . objectStoreNames . contains ( STORE _NAME ) ) {
request . result . createObjectStore ( STORE _NAME , { keyPath : 'id' } ) ;
}
} ;
request . onsuccess = ( ) => resolve ( request . result ) ;
request . onerror = ( ) => reject ( request . error ) ;
} ) ;
}
toBase64 ( bytes ) {
let binary = '' ;
for ( const byte of bytes ) {
binary += String . fromCharCode ( byte ) ;
}
return btoa ( binary ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : Ed25519KeypairService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : Ed25519KeypairService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : Ed25519KeypairService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/ * *
* 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" ) .
* /
class JwtService {
decode ( token ) {
const parts = token . split ( '.' ) ;
if ( parts . length !== 3 ) {
return null ;
}
try {
const payload = this . base64UrlDecode ( parts [ 1 ] ) ;
const claims = JSON . parse ( payload ) ;
return this . isJwtClaims ( claims ) ? claims : null ;
}
catch {
return null ;
}
}
isExpired ( claims , skewSeconds = 0 ) {
return claims . exp * 1000 <= Date . now ( ) + skewSeconds * 1000 ;
}
isJwtClaims ( value ) {
if ( ! value || typeof value !== 'object' ) {
return false ;
}
const claims = value ;
return typeof claims . sub === 'string' && typeof claims . role === 'string' && typeof claims . exp === 'number' ;
}
base64UrlDecode ( input ) {
const base64 = input . replace ( /-/g , '+' ) . replace ( /_/g , '/' ) . padEnd ( input . length + ( ( 4 - ( input . length % 4 ) ) % 4 ) , '=' ) ;
return decodeURIComponent ( escape ( atob ( base64 ) ) ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : JwtService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : JwtService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : JwtService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
const TOKEN _STORAGE _KEY = 'ed25519AdminToken' ;
const REFRESH _STORAGE _KEY = 'ed25519AdminRefreshToken' ;
/** Refresh this long before actual expiry, so a request never races an expiring token. */
const REFRESH _SKEW _MS = 60_000 ;
/ * *
* 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 .
* /
class SessionService {
constructor ( ) {
this . jwt = new JwtService ( ) ;
this . tokenSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "tokenSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . refreshTokenSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "refreshTokenSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . claimsSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "claimsSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . statusSignal = signal ( 'unknown' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "statusSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . token = this . tokenSignal . asReadonly ( ) ;
this . claims = this . claimsSignal . asReadonly ( ) ;
this . status = this . statusSignal . asReadonly ( ) ;
this . isAuthenticated = computed ( ( ) => this . statusSignal ( ) === 'authenticated' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "isAuthenticated" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . role = computed ( ( ) => this . claimsSignal ( ) ? . role ? ? null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "role" } ] : /* istanbul ignore next */ [ ] ) ) ;
}
/** Called once by AuthService on init to wire up the refresh trigger without a circular DI dependency. */
onRefreshDue ( callback ) {
this . refreshCallback = callback ;
}
/** Restores session state from persisted storage. Returns true if a (possibly expired) session was found. */
restore ( ) {
this . statusSignal . set ( 'restoring' ) ;
const token = this . readStorage ( TOKEN _STORAGE _KEY ) ;
const refreshToken = this . readStorage ( REFRESH _STORAGE _KEY ) ;
if ( ! token || ! refreshToken ) {
this . statusSignal . set ( 'unauthenticated' ) ;
return false ;
}
const claims = this . jwt . decode ( token ) ;
if ( ! claims ) {
this . clear ( ) ;
return false ;
}
this . tokenSignal . set ( token ) ;
this . refreshTokenSignal . set ( refreshToken ) ;
this . claimsSignal . set ( claims ) ;
if ( this . jwt . isExpired ( claims ) ) {
this . statusSignal . set ( 'expired' ) ;
}
else {
this . statusSignal . set ( 'authenticated' ) ;
this . scheduleRefresh ( claims ) ;
}
return true ;
}
activate ( tokens ) {
const claims = this . jwt . decode ( tokens . token ) ;
if ( ! claims ) {
throw new Error ( 'Received a malformed JWT from the auth backend.' ) ;
}
this . tokenSignal . set ( tokens . token ) ;
this . refreshTokenSignal . set ( tokens . refreshToken ) ;
this . claimsSignal . set ( claims ) ;
this . statusSignal . set ( 'authenticated' ) ;
this . writeStorage ( TOKEN _STORAGE _KEY , tokens . token ) ;
this . writeStorage ( REFRESH _STORAGE _KEY , tokens . refreshToken ) ;
this . scheduleRefresh ( claims ) ;
}
getRefreshToken ( ) {
return this . refreshTokenSignal ( ) ;
}
markExpired ( ) {
this . statusSignal . set ( 'expired' ) ;
this . clearRefreshTimer ( ) ;
}
clear ( ) {
this . tokenSignal . set ( null ) ;
this . refreshTokenSignal . set ( null ) ;
this . claimsSignal . set ( null ) ;
this . statusSignal . set ( 'unauthenticated' ) ;
this . removeStorage ( TOKEN _STORAGE _KEY ) ;
this . removeStorage ( REFRESH _STORAGE _KEY ) ;
this . clearRefreshTimer ( ) ;
}
scheduleRefresh ( claims ) {
this . clearRefreshTimer ( ) ;
const expiresInMs = claims . exp * 1000 - Date . now ( ) ;
const refreshInMs = Math . max ( expiresInMs - REFRESH _SKEW _MS , 5_000 ) ;
this . refreshTimer = setTimeout ( ( ) => this . refreshCallback ? . ( ) , refreshInMs ) ;
}
clearRefreshTimer ( ) {
if ( this . refreshTimer ) {
clearTimeout ( this . refreshTimer ) ;
this . refreshTimer = undefined ;
}
}
readStorage ( key ) {
return typeof localStorage === 'undefined' ? null : localStorage . getItem ( key ) ;
}
writeStorage ( key , value ) {
if ( typeof localStorage !== 'undefined' ) {
localStorage . setItem ( key , value ) ;
}
}
removeStorage ( key ) {
if ( typeof localStorage !== 'undefined' ) {
localStorage . removeItem ( key ) ;
}
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : SessionService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : SessionService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : SessionService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/ * *
* 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 ` .
* /
class AuthService {
constructor ( ) {
this . api = inject ( AuthApiService ) ;
this . keypair = inject ( Ed25519KeypairService ) ;
this . session = inject ( SessionService ) ;
this . loginPhaseSignal = signal ( 'idle' , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "loginPhaseSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . lastErrorSignal = signal ( null , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "lastErrorSignal" } ] : /* istanbul ignore next */ [ ] ) ) ;
this . loginPhase = this . loginPhaseSignal . asReadonly ( ) ;
this . lastError = this . lastErrorSignal . asReadonly ( ) ;
this . session . onRefreshDue ( ( ) => this . refresh ( ) . subscribe ( ) ) ;
}
/** Restores a persisted session on app bootstrap. Call once from an APP_INITIALIZER or root component. */
restoreSession ( ) {
this . session . restore ( ) ;
}
login ( ) {
this . lastErrorSignal . set ( null ) ;
this . loginPhaseSignal . set ( 'requesting-challenge' ) ;
return this . api . requestChallenge ( ) . pipe ( switchMap ( challenge => this . signChallenge ( challenge . nonce ) . pipe ( switchMap ( ( { publicKeyBase64 , signature } ) => {
this . loginPhaseSignal . set ( 'verifying' ) ;
return this . api . verifySignature ( { publicKey : publicKeyBase64 , signature , nonce : challenge . nonce } ) ;
} ) ) ) , tap ( tokens => {
this . session . activate ( tokens ) ;
this . loginPhaseSignal . set ( 'done' ) ;
} ) , catchError ( error => this . handleAuthError ( error , 'invalid-signature' ) ) ) ;
}
refresh ( ) {
const refreshToken = this . session . getRefreshToken ( ) ;
if ( ! refreshToken ) {
this . session . markExpired ( ) ;
return throwError ( ( ) => this . toAuthError ( { code : 'session-expired' , message : 'No refresh token available.' } ) ) ;
}
return this . api . refresh ( { refreshToken } ) . pipe ( tap ( tokens => this . session . activate ( tokens ) ) , catchError ( error => this . handleAuthError ( error , 'session-expired' , ( ) => this . session . markExpired ( ) ) ) ) ;
}
logout ( ) {
const refreshToken = this . session . getRefreshToken ( ) ;
this . session . clear ( ) ;
if ( ! refreshToken ) {
return new Observable ( subscriber => {
subscriber . next ( ) ;
subscriber . complete ( ) ;
} ) ;
}
return this . api . logout ( refreshToken ) . pipe ( catchError ( ( ) => throwError ( ( ) => null ) ) ) ;
}
signChallenge ( nonce ) {
this . loginPhaseSignal . set ( 'signing' ) ;
return new Observable ( subscriber => {
this . keypair
. getOrCreateKeyPair ( )
. then ( ( { publicKeyBase64 } ) => this . keypair . sign ( nonce ) . then ( signature => {
subscriber . next ( { publicKeyBase64 , signature } ) ;
subscriber . complete ( ) ;
} ) )
. catch ( error => subscriber . error ( error ) ) ;
} ) ;
}
handleAuthError ( error , fallbackCode , onError ) {
onError ? . ( ) ;
return throwError ( ( ) => this . toAuthError ( this . toAuthErrorShape ( error , fallbackCode ) ) ) ;
}
toAuthErrorShape ( error , fallbackCode ) {
if ( error instanceof HttpErrorResponse ) {
const bodyCode = error . error ? . error ? . code ;
const code = authErrorCodeFromBackendCode ( bodyCode ) ? ? authErrorCodeFromStatus ( error . status ) ;
return { code , message : error . message , status : error . status } ;
}
if ( error instanceof Error ) {
return { code : fallbackCode , message : error . message } ;
}
return { code : fallbackCode , message : 'Unknown authentication error.' } ;
}
toAuthError ( error ) {
this . lastErrorSignal . set ( error ) ;
return error ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] , ctorParameters : ( ) => [ ] } ) ;
const ROLE _PERMISSIONS = {
Owner : [ 'backoffice.read' , 'backoffice.write' , 'builder.read' , 'builder.write' , 'users.manage' , 'settings.manage' ] ,
Administrator : [ 'backoffice.read' , 'backoffice.write' , 'builder.read' , 'builder.write' , 'users.manage' ] ,
Editor : [ 'backoffice.read' , 'backoffice.write' , 'builder.read' , 'builder.write' ] ,
Support : [ 'backoffice.read' ] ,
ReadOnly : [ 'backoffice.read' , 'builder.read' ]
} ;
/ * *
* 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 .
* /
class PermissionService {
constructor ( ) {
this . session = inject ( SessionService ) ;
this . permissions = computed ( ( ) => {
const role = this . session . role ( ) ;
return role ? ROLE _PERMISSIONS [ role ] : [ ] ;
} , /* @ts-ignore */
... ( ngDevMode ? [ { debugName : "permissions" } ] : /* istanbul ignore next */ [ ] ) ) ;
}
has ( permission ) {
return this . permissions ( ) . includes ( permission ) ;
}
hasAny ( permissions ) {
return permissions . some ( permission => this . has ( permission ) ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : PermissionService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : PermissionService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : PermissionService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
/ * *
* 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 .
* /
class AuthFacade {
constructor ( ) {
this . auth = inject ( AuthService ) ;
this . session = inject ( SessionService ) ;
this . permissions = inject ( PermissionService ) ;
this . router = inject ( Router ) ;
this . isAuthenticated = this . session . isAuthenticated ;
this . status = this . session . status ;
this . role = this . session . role ;
this . loginPhase = this . auth . loginPhase ;
this . lastError = this . auth . lastError ;
}
restoreSession ( ) {
this . auth . restoreSession ( ) ;
}
login ( onSuccessRedirectTo ) {
this . auth . login ( ) . subscribe ( {
next : ( ) => {
if ( onSuccessRedirectTo ) {
this . router . navigateByUrl ( onSuccessRedirectTo ) ;
}
} ,
error : ( ) => {
const code = this . auth . lastError ( ) ? . code ? ? 'unauthorized' ;
this . router . navigate ( [ '/admin-login/error' , code ] ) ;
}
} ) ;
}
logout ( redirectTo = '/admin-login' ) {
this . auth
. logout ( )
. pipe ( finalize ( ( ) => this . router . navigateByUrl ( redirectTo ) ) )
. subscribe ( { error : ( ) => undefined } ) ;
}
can ( permission ) {
return this . permissions . has ( permission ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthFacade , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthFacade , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : AuthFacade , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
class Ed25519VerificationService {
}
/ * *
* 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 .
* /
class NoopEd25519VerificationService {
requestChallenge ( ) {
return throwError ( ( ) => new Error ( 'Ed25519 challenge endpoint is not yet available from the backend.' ) ) ;
}
verify ( _response ) {
return throwError ( ( ) => new Error ( 'Ed25519 verification endpoint is not yet available from the backend.' ) ) ;
}
static { this . ɵfac = i0 . ɵɵngDeclareFactory ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : NoopEd25519VerificationService , deps : [ ] , target : i0 . ɵɵFactoryTarget . Injectable } ) ; }
static { this . ɵprov = i0 . ɵɵngDeclareInjectable ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : NoopEd25519VerificationService , providedIn : 'root' } ) ; }
}
i0 . ɵɵngDeclareClassMetadata ( { minVersion : "12.0.0" , version : "22.0.8" , ngImport : i0 , type : NoopEd25519VerificationService , decorators : [ {
type : Injectable ,
args : [ { providedIn : 'root' } ]
} ] } ) ;
// @marketplaces/auth — public API barrel.
// Two independent auth mechanisms, per ADR-0001 (marketplaces repo:
// docs/context/adrs/ADR-0001-extract-auth-and-payment-into-shared-marketplaces-packages.md):
// - telegram/ — live Telegram QR/session auth (customer + admin)
// - ed25519/ — future Ed25519 challenge/response admin auth (backend not shipped yet)
// Provide AUTH_API_URL (and optionally TELEGRAM_BOT_USERNAME) from the consuming app's config.
/ * *
* Generated bundle index . Do not edit .
* /
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 } ;
//# sourceMappingURL=marketplaces-auth.mjs.map