fix(auth): route admin/customer Telegram QR login to the shared auth origin
Some checks failed
Architecture Governance / architecture (push) Failing after 2m51s
Deploy Frontend / deploy (push) Failing after 1m57s

AUTH_API_URL was wired to ApiConfigService.getBaseUrl() (the per-tenant
content origin, e.g. api.gorbushka.market), so admin/customer QR-login
session creation POSTed to a host with no /users/sessions route.

Auth is a single shared cross-tenant service (like payment's
qr.vitanova.network), not tenant-scoped - confirmed against the
pre-tenant-refactor state (commit a59ffbca) where every brand's
environment file carried the identical fixed authApiUrl, and against a
live POST to users.vitanova.network:456/users/sessions.

Restores authApiUrl as a fixed constant in both environment files and
wires AUTH_API_URL/MARKETPLACES_AUTH_CONFIG.apiUrl to it, mirroring the
existing qrApiUrl/provideMarketplacesPayment pattern. Also provides
MARKETPLACES_AUTH_CONFIG.marketplaceDomain via TenantResolverService's
existing getBaseDomain() so the X-Marketplace-Domain header stays
normalized instead of falling back to raw, unnormalized
location.hostname (it was never provided before, so that fallback was
always in effect).

No package edits, no path/method/body change, no QR/Telegram flow
change. tenantApiTemplate/tenantApiBaseUrls/ApiConfigService/
TenantResolverService untouched - still drive content-API resolution
only.

Known remaining blocker (server-side, tracked in vitanovaPackages
BACKEND-TODO.md): users.vitanova.network:456 CORS-rejects the
admin.gorbushka.market origin outright (403 on preflight, no allow
headers) while novo.market gets a full grant - this fix alone will not
make browser login work on gorbushka domains until that origin
allowlist is updated.
This commit is contained in:
sdarbinyan
2026-08-24 14:14:30 +04:00
parent c2a56571af
commit 84253012c1
3 changed files with 20 additions and 9 deletions

View File

@@ -8,12 +8,11 @@ import { apiErrorInterceptor } from './core/interceptors/api-error.interceptor';
import { apiBaseUrlInterceptor } from './interceptors/api-base-url.interceptor'; import { apiBaseUrlInterceptor } from './interceptors/api-base-url.interceptor';
import { apiHeadersInterceptor } from './interceptors/api-headers.interceptor'; import { apiHeadersInterceptor } from './interceptors/api-headers.interceptor';
import { mockDataInterceptor } from './interceptors/mock-data.interceptor'; import { mockDataInterceptor } from './interceptors/mock-data.interceptor';
import { adminAuthHeadersInterceptor, Ed25519VerificationService, NoopEd25519VerificationService, AUTH_API_URL, TELEGRAM_BOT_USERNAME } from '@marketplaces/auth'; import { adminAuthHeadersInterceptor, Ed25519VerificationService, NoopEd25519VerificationService, AUTH_API_URL, TELEGRAM_BOT_USERNAME, MARKETPLACES_AUTH_CONFIG, MarketplacesAuthConfig } from '@marketplaces/auth';
import { provideMarketplacesPayment } from '@marketplaces/payment'; import { provideMarketplacesPayment } from '@marketplaces/payment';
import { provideServiceWorker } from '@angular/service-worker'; import { provideServiceWorker } from '@angular/service-worker';
import { MediaRepository } from './core/media/media-repository'; import { MediaRepository } from './core/media/media-repository';
import { MockMediaRepository } from './core/media/mock-media-repository.service'; import { MockMediaRepository } from './core/media/mock-media-repository.service';
import { ApiConfigService } from './core/config/api-config.service';
import { TenantResolverService } from './core/config/tenant-resolver.service'; import { TenantResolverService } from './core/config/tenant-resolver.service';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { MOCK_GATEWAY_PROVIDERS } from './mock-gateway.providers'; import { MOCK_GATEWAY_PROVIDERS } from './mock-gateway.providers';
@@ -31,12 +30,22 @@ export const appConfig: ApplicationConfig = {
// other interceptor has run, and normalizes whatever actually came back. // other interceptor has run, and normalizes whatever actually came back.
withInterceptors([mockDataInterceptor, apiBaseUrlInterceptor, apiHeadersInterceptor, adminAuthHeadersInterceptor, cacheInterceptor, apiErrorInterceptor]) withInterceptors([mockDataInterceptor, apiBaseUrlInterceptor, apiHeadersInterceptor, adminAuthHeadersInterceptor, cacheInterceptor, apiErrorInterceptor])
), ),
{ // authApiUrl ('https://users.vitanova.network:456') is a single shared
provide: AUTH_API_URL, // cross-tenant service, same shape as qrApiUrl below - NOT the per-tenant
useFactory: (apiConfig: ApiConfigService) => apiConfig.getBaseUrl(), // apiConfig.getBaseUrl(). That was the bug: AUTH_API_URL used to read the
deps: [ApiConfigService] // tenant content origin (api.{baseDomain}), which has no /users/sessions
}, // endpoint. Confirmed against the pre-split reference build (novo.market)
// and a live POST against users.vitanova.network:456/users/sessions.
{ provide: AUTH_API_URL, useValue: environment.authApiUrl },
{ provide: TELEGRAM_BOT_USERNAME, useValue: environment.telegramBot }, { provide: TELEGRAM_BOT_USERNAME, useValue: environment.telegramBot },
{
provide: MARKETPLACES_AUTH_CONFIG,
useFactory: (tenantResolver: TenantResolverService): MarketplacesAuthConfig => ({
apiUrl: environment.authApiUrl,
marketplaceDomain: () => tenantResolver.getBaseDomain(),
}),
deps: [TenantResolverService]
},
// useFactory, not useClass: @marketplaces/auth ships plain tsc output, not // useFactory, not useClass: @marketplaces/auth ships plain tsc output, not
// Angular Package Format, so it carries no baked-in Ivy DI metadata for // Angular Package Format, so it carries no baked-in Ivy DI metadata for
// this class. useClass forces Angular to JIT-compile it at runtime, which // this class. useClass forces Angular to JIT-compile it at runtime, which
@@ -49,8 +58,8 @@ export const appConfig: ApplicationConfig = {
// apiUrl: environment.qrApiUrl ('https://qr.vitanova.network/api') is the // apiUrl: environment.qrApiUrl ('https://qr.vitanova.network/api') is the
// same "central payment service" the legacy /qr and // same "central payment service" the legacy /qr and
// /card/{partnerId}/{orderId} endpoints already used (api.service.ts) - // /card/{partnerId}/{orderId} endpoints already used (api.service.ts) -
// one service shared across every tenant, unlike the per-tenant // one service shared across every tenant - same shape as AUTH_API_URL
// AUTH_API_URL above. Stripped the trailing /api here: the package's own // above. Stripped the trailing /api here: the package's own
// default paymentsPath is '/api/v1/payments', so passing qrApiUrl // default paymentsPath is '/api/v1/payments', so passing qrApiUrl
// unchanged would double it to .../api/api/v1/payments. Confirmed by // unchanged would double it to .../api/api/v1/payments. Confirmed by
// reading the package's baseUrl() directly (apiUrl + paymentsPath, // reading the package's baseUrl() directly (apiUrl + paymentsPath,

View File

@@ -11,6 +11,7 @@ export const environment = {
theme: 'dexar', theme: 'dexar',
apiUrl: '/api', apiUrl: '/api',
qrApiUrl: 'https://qr.vitanova.network/api', qrApiUrl: 'https://qr.vitanova.network/api',
authApiUrl: 'https://users.vitanova.network:456',
logo: '/icons/icon-192x192.png', logo: '/icons/icon-192x192.png',
contactEmail: 'info@dexarmarket.ru', contactEmail: 'info@dexarmarket.ru',
supportEmail: 'info@dexarmarket.ru', supportEmail: 'info@dexarmarket.ru',

View File

@@ -12,6 +12,7 @@ export const environment = {
theme: 'dexar', theme: 'dexar',
apiUrl: '/api', apiUrl: '/api',
qrApiUrl: 'https://qr.vitanova.network/api', qrApiUrl: 'https://qr.vitanova.network/api',
authApiUrl: 'https://users.vitanova.network:456',
logo: '/icons/icon-192x192.png', logo: '/icons/icon-192x192.png',
contactEmail: 'info@dexarmarket.ru', contactEmail: 'info@dexarmarket.ru',
supportEmail: 'info@dexarmarket.ru', supportEmail: 'info@dexarmarket.ru',