fix(auth): break circular DI that logged every returning session out
apiHeadersInterceptor injected @marketplaces/auth's AuthService to attach a WebSessionID header. AuthService's own constructor makes a synchronous GET /users/sessions/:id call to verify a persisted session, which runs through this exact interceptor - Angular throws NG0200 (circular dependency) mid-construction, silently swallowed by the package's catchError(() => of(null)), read as "session invalid," and the cookie gets cleared on every single page load. This is what was gating the architecture-governance e2e job on Gitea (3 checkout tests failing on a disabled QR button). Session-check requests are the identity mechanism itself and never needed that header - skip AuthService injection for them instead. Also fixes mock-data.interceptor's session-check mock, which required 3 polls before reporting an id active with no way to represent a returning session with an already-valid cookie - not the actual trigger for this bug (useMockData is false in the dev config CI uses), but a real gap in the mock's fidelity worth closing while in this file.
This commit is contained in:
@@ -38,6 +38,21 @@ function getAnonymousSessionId(): string {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @marketplaces/auth's AuthService checks for a persisted session in its own
|
||||
* constructor (a synchronous HTTP call to GET /users/sessions/:id before the
|
||||
* constructor returns). If this interceptor injects AuthService for that
|
||||
* exact call, Angular sees AuthService requesting itself mid-construction
|
||||
* and throws NG0200 (circular dependency) - silently, since it's swallowed
|
||||
* by TelegramSessionApiService's catchError(() => of(null)), which reads as
|
||||
* "session invalid" and logs the user straight back out on every load.
|
||||
* These endpoints are the identity mechanism itself (the session id is
|
||||
* already the URL/body), so they never needed a WebSessionID header from an
|
||||
* existing session in the first place - skipping AuthService injection here
|
||||
* is correct, not a workaround.
|
||||
*/
|
||||
const AUTH_SESSION_PATH = '/users/sessions';
|
||||
|
||||
export const apiHeadersInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const apiConfig = inject(ApiConfigService);
|
||||
if (!apiConfig.isApiRequest(req.url)) {
|
||||
@@ -46,12 +61,10 @@ export const apiHeadersInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
|
||||
const locationService = inject(LocationService);
|
||||
const languageService = inject(LanguageService);
|
||||
const authService = inject(AuthService);
|
||||
|
||||
const regionId = locationService.regionId();
|
||||
const lang = languageService.currentLanguage();
|
||||
const currency = languageService.currentCurrency();
|
||||
const session = authService.session();
|
||||
|
||||
let headers = req.headers;
|
||||
|
||||
@@ -62,7 +75,12 @@ export const apiHeadersInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
headers = headers.set('X-Language', LANG_HEADER_MAP[lang] ?? lang.toUpperCase());
|
||||
}
|
||||
headers = headers.set('Currency', currency || 'RUB');
|
||||
headers = headers.set('WebSessionID', session?.sessionId || getAnonymousSessionId());
|
||||
|
||||
if (!req.url.includes(AUTH_SESSION_PATH)) {
|
||||
const authService = inject(AuthService);
|
||||
const session = authService.session();
|
||||
headers = headers.set('WebSessionID', session?.sessionId || getAnonymousSessionId());
|
||||
}
|
||||
|
||||
return next(req.clone({ headers }));
|
||||
};
|
||||
|
||||
@@ -741,10 +741,16 @@ export const mockDataInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const userSessionMatch = url.match(/\/users\/sessions\/([^/?]+)$/);
|
||||
if (userSessionMatch && req.method === 'GET') {
|
||||
const webSessionID = decodeURIComponent(userSessionMatch[1]);
|
||||
// An id never seen via POST /users/sessions didn't start a fresh QR
|
||||
// login in this session - it's a cookie carried over from an earlier
|
||||
// visit (AuthService.checkSession's one-shot check on page load), which
|
||||
// a real backend would already recognize. Only ids the mock itself put
|
||||
// through the polling flow need the checks>=3 gate below.
|
||||
const isReturningSession = !mockWebSessionChecks.has(webSessionID);
|
||||
const checks = (mockWebSessionChecks.get(webSessionID) ?? 0) + 1;
|
||||
mockWebSessionChecks.set(webSessionID, checks);
|
||||
|
||||
if (checks >= 3) {
|
||||
if (isReturningSession || checks >= 3) {
|
||||
return respond({
|
||||
webSessionID,
|
||||
status: true,
|
||||
|
||||
Reference in New Issue
Block a user