feat: extract auth into @marketplaces/auth package, add backoffice admin provisioning spec

- ADR-0001: decision to extract auth/payment into shared @marketplaces/* packages
- Scaffold packages/auth, packages/payment; @marketplaces/auth now holds the real
  telegram (customer+admin QR/session) and ed25519 (future admin challenge/response)
  auth implementation, pushed to sources.vitanova.network/sdarbinyan/vitanovaPackages
- Rewire ~30 call sites to import from @marketplaces/auth; delete migrated originals
  from core/auth, core/admin-auth, services/, models/
- Replace environment coupling with AUTH_API_URL/TELEGRAM_BOT_USERNAME injection
  tokens and isDevMode(); wired as file:packages/auth pending registry publish
- Add TRACK-S §8: bootstrap per-marketplace admin login + marketplace-scoped
  sub-admin invite/role endpoints
- Build, arch:check:boundaries, and full test suite (103/103) all green

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-18 01:05:16 +04:00
parent 23060261c7
commit 14c72d1a6a
62 changed files with 420 additions and 127 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
# Compiled output
/dist
packages/*/dist
/tmp
/out-tsc
/bazel-out

View File

@@ -0,0 +1,50 @@
# @marketplaces/auth & @marketplaces/payment — build, version, publish, consume
See [ADR-0001](context/adrs/ADR-0001-extract-auth-and-payment-into-shared-marketplaces-packages.md) for why. This doc is the how.
## Current state
[packages/](../packages) here is now just a reference copy (`package.json`/`tsconfig.json`, no CI) — the real source, CI, and versioning live in the pushed [vitanovaPackages](https://sources.vitanova.network/sdarbinyan/vitanovaPackages.git) repo (§1). No app code has moved yet — `src/app/core/auth`, `src/app/core/admin-auth`, `src/app/core/finance`, `src/app/core/pricing` are still the live implementation in `marketplaces`.
## 1. Target repo
Pushed: [sources.vitanova.network/sdarbinyan/vitanovaPackages](https://sources.vitanova.network/sdarbinyan/vitanovaPackages.git) — single monorepo (npm workspaces) hosting both `packages/auth` and `packages/payment`, `main` branch, initial scaffold commit `5567154`. The `packages/*` scaffold here in `marketplaces` stays as the pre-push staging copy; the pushed repo is now the source of truth for the package source itself.
## 2. Versioning
Both packages live in one monorepo (vitanovaPackages, npm workspaces), so versioning uses [Changesets](https://github.com/changesets/changesets), not per-package semantic-release — Changesets is built for exactly this "many packages, one repo, independent versions" shape. A PR that changes `packages/auth` adds a changeset file (`npx changeset` from repo root, picks package + bump type + writes a short description) alongside the code change.
## 3. Publishing (CI)
`vitanovaPackages/.github/workflows/release.yml`: on push to `main`, installs, builds, tests, then `changesets/action`:
- if unreleased changesets exist, opens/updates a "Version Packages" PR that bumps `package.json` versions and writes changelogs,
- once that PR is merged, the next push to `main` publishes the bumped package(s) to the registry.
Requires two repo secrets: `NPM_TOKEN` (publish token) and `GITHUB_TOKEN` (auto-provided on GitHub Actions; use the Gitea/Forgejo equivalent if this host isn't GitHub-Actions-native — check with whoever administers `sources.vitanova.network`).
Registry choice — pick one before first publish:
- **npm private scope** (`@marketplaces` org on npmjs.com) — simplest, works with the workflow as-is.
- **GitHub Packages** — swap `registry-url` in the workflow to `https://npm.pkg.github.com`.
- **Self-hosted (Verdaccio) on the dev server** — point `registry-url` at the server's registry endpoint; requires the registry to be stood up on `213.21.246.138` first (not done yet).
## 4. Consuming from `marketplaces` (and other projects)
Once published:
```bash
npm install @marketplaces/auth @marketplaces/payment
```
```ts
import { ... } from '@marketplaces/auth';
```
Pin exact versions (no `^`/`~` ranges) per ADR-0001's consequence about registry-outage blast radius — bump deliberately, not automatically, on this side.
[renovate.json](../renovate.json) at repo root opens a grouped PR whenever either package publishes a new version — review and merge it manually (`automerge: false`), it does not land unattended.
## 5. Migration cutover
**Auth: done.** `@marketplaces/auth` now holds the real implementation — two independent modules, `telegram/` (live Telegram QR/session auth, customer + admin) and `ed25519/` (future challenge/response admin auth, backend not shipped). Environment coupling was replaced with `AUTH_API_URL`/`TELEGRAM_BOT_USERNAME` injection tokens, provided from `app.config.ts`; `environment.production` became Angular's `isDevMode()`. `AdminPermissionsService` and `requireAdminPermission` stayed in `marketplaces` (`core/admin-auth/`) since they read this app's mock Users domain, not a portable auth concern. All ~30 call sites now import `@marketplaces/auth`; the old `src/app/core/auth`, `src/app/core/admin-auth/admin-auth.service.ts` (+ interceptor, ed25519 files), `src/app/services/auth.service.ts`, `src/app/services/telegram-session-api.service.ts`, and `src/app/models/auth.model.ts`/`admin-auth.model.ts` are deleted. `npm run build`, `npm run arch:check:boundaries`, and `npm test` (103/103) all pass. Wired as a `file:packages/auth` dependency until the registry (§3) is live — swap to a real semver range once published.
**Payment: not started.** `core/finance`/`core/pricing` still live in `marketplaces`, same process as above once prioritized.

View File

@@ -75,7 +75,35 @@ Required before: bank/payment detail changes (Phase 5 §5), production launch (P
Customer/seller PII is exposed only to roles that need it for their scope (e.g. `FINANCE_VIEWER` sees payout totals, not raw bank account numbers unless `FINANCE_MANAGER`+). Export endpoints (`GET .../export`) are themselves audit-logged actions per §3.
## 8. What the frontend will start doing once this ships
## 8. Initial admin provisioning & self-service admin management
Each marketplace ships with one bootstrap `MARKETPLACE_ADMIN` account, seeded at provisioning time (Phase 9 launch step):
- `login` = marketplace slug (`projectName`)
- `password` = `{projectName}2026$`, flagged `mustChangePassword: true`
- Login succeeds but every non-auth request 403s with `PASSWORD_CHANGE_REQUIRED` until password is changed.
```
POST /api/identity/v1/session/change-password { currentPassword, newPassword }
```
A `MARKETPLACE_ADMIN` can then provision sub-admins scoped to their own marketplace only — mirrors the seller-team invite pattern in [Phase 5](PHASE-5-SELLER-PORTAL-CONTRACT.md) (`POST /api/seller/v1/team/invite`):
```
POST /api/admin/v2/team/invite { email, role: MarketplaceRole, marketplaceId }
GET /api/admin/v2/team?marketplaceId=
PATCH /api/admin/v2/team/{userId} { role }
DELETE /api/admin/v2/team/{userId}
```
Invariants:
- `role` must be one of the `MarketplaceRole` set (§1) — never `PlatformRole`. Backend rejects any attempt to grant a platform-scope role through this endpoint (`403 SCOPE_ESCALATION_DENIED`).
- `marketplaceId` is forced server-side to the caller's own tenant scope — request body value is ignored/validated, never trusted.
- Every invite/role-change/removal is an audit-logged action (§3, `action: 'admin_team.invited' | 'admin_team.role_changed' | 'admin_team.removed'`).
- Role grants at `MARKETPLACE_ADMIN` level require step-up auth (§6).
- Invited admins get their own credentials (email + set-password flow), not the shared bootstrap login — the bootstrap account is for first login only and should be rotated/retired once real admins exist.
## 9. What the frontend will start doing once this ships
- Route guards and action-level permission checks across the entire backoffice — currently none exist.
- Backoffice **Audit & Security** section (missing from admin nav today): role changes, sensitive actions, login/security events, exports.

View File

@@ -0,0 +1,38 @@
---
id: ADR-0001
title: Extract auth and payment into shared @marketplaces packages
status: active
date: 2026-08-17
supersedes: []
tags: [architecture, auth, payment, monorepo]
---
# ADR-0001: Extract auth and payment into shared @marketplaces packages
## Context
`marketplaces` currently owns auth end-to-end: customer auth (`core/auth` — VK ID, OTP, session, facade), admin auth (`core/admin-auth` — ed25519-verified admin sessions, permission guards, interceptor), and a legacy `services/auth.service.ts`. Payment/finance logic (`core/finance`, `core/pricing`) is server-owned per [Phase 1](../../backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md) and [Phase 7](../../backend/PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md) contracts — the frontend piece is thin (gateways/tokens, no business logic).
Multiple marketplace projects beyond this repo need the same auth and payment client logic. Duplicating it per-project drifts fast (auth bugs get fixed in one place, not others) and blocks a consistent security posture across projects — directly relevant to [TRACK-S-SECURITY-RBAC-CONTRACT.md](../../backend/TRACK-S-SECURITY-RBAC-CONTRACT.md), which already treats auth/RBAC as the single most serious cross-cutting concern.
## Decision
Extract auth and payment client logic into two standalone, independently versioned npm packages:
- `@marketplaces/auth` — customer auth (VK ID/OTP/session), admin auth (ed25519 verification, permission guards, interceptors), token/session management.
- `@marketplaces/payment` — payment/finance client gateways, FX/pricing models, checkout client contracts (thin — business logic stays backend per Phase 1/7).
Each package:
1. Lives in its own git repo (handed over separately; this repo does not host it long-term).
2. Is consumed by `marketplaces` (and other projects) as an installed node_modules dependency — imported, never copy-pasted.
3. Is versioned with semver; CI on the package repo auto-bumps and publishes on push to `main`, driven by conventional commit prefixes already used in this repo (`feat:`/`fix:`/etc — semantic-release reads these directly).
4. Ships with its own test suite; `marketplaces` treats it as a black-box dependency, not source to edit in place.
Rollout order: scaffold packages and CI in this repo first (reversible, local-only) → hand over target git repo → publish → migrate `marketplaces` call sites to import from the package → delete the in-repo originals only after the app builds and passes tests against the package.
## Consequences
- `marketplaces` loses direct edit access to auth/payment source — changes go through the package's own repo/PR/release cycle. Slower iteration, but consistent behavior across all consuming projects.
- ~30 call sites in `marketplaces` (see `core/auth`, `core/admin-auth`, `services/auth.service.ts`, interceptors) need import rewiring during migration — tracked as follow-up work, not done in this ADR.
- New failure mode: package registry/CI outage blocks `marketplaces` builds if a version bump lands mid-incident. Pin exact versions, do not use floating ranges, to keep this bounded.
- [TRACK-S-SECURITY-RBAC-CONTRACT.md](../../backend/TRACK-S-SECURITY-RBAC-CONTRACT.md) §8 (admin provisioning) becomes package-owned behavior once migrated — that doc's endpoint contracts stay backend-side and unaffected, only the frontend client implementation moves.

View File

@@ -4,3 +4,4 @@
{"id":"PV-20260713T000000Z-0004","subject":"translatable-fields","predicate":"must-be-modeled-as","object":"generic translations.{lang} map so adding/removing a language automatically exposes/removes translation fields across all translatable objects","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["i18n","constraint"]}
{"id":"PV-20260713T000000Z-0005","subject":"admin-app","predicate":"is-isolated-from","object":"marketplace storefront bundle: admin code never ships to storefront and vice versa, though they may share a domain","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["admin","security"]}
{"id":"PV-20260713T000000Z-0006","subject":"widgets","predicate":"must-not-own","object":"page spacing or page width; the renderer owns sections, spacing, and page width, widgets own only their internal layout","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["widgets","layout"]}
{"id":"PV-20260818T001500Z-a1f3","subject":"auth-and-payment-client-logic","predicate":"is-decided-to-extract-into","object":"standalone versioned npm packages @marketplaces/auth and @marketplaces/payment, installed as dependencies rather than edited in-repo","src":["docs/context/adrs/ADR-0001-extract-auth-and-payment-into-shared-marketplaces-packages.md"],"status":"active","kind":"decision","updated_at":"2026-08-18T00:15:00Z","confidence":"high","tags":["architecture","auth","payment","decision"]}

15
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"@angular/platform-browser": "22.0.8",
"@angular/router": "22.0.8",
"@angular/service-worker": "22.0.8",
"@marketplaces/auth": "file:packages/auth",
"rxjs": "~7.8.0",
"tslib": "^2.8.0",
"zone.js": "~0.16.0"
@@ -1968,6 +1969,10 @@
"win32"
]
},
"node_modules/@marketplaces/auth": {
"resolved": "packages/auth",
"link": true
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -9234,6 +9239,16 @@
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.16.0.tgz",
"integrity": "sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==",
"license": "MIT"
},
"packages/auth": {
"name": "@marketplaces/auth",
"version": "0.1.0",
"license": "UNLICENSED",
"peerDependencies": {
"@angular/common": ">=22.0.0",
"@angular/core": ">=22.0.0",
"rxjs": ">=7.8.0"
}
}
}
}

View File

@@ -22,6 +22,7 @@
},
"private": true,
"dependencies": {
"@marketplaces/auth": "file:packages/auth",
"@angular/animations": "22.0.8",
"@angular/cdk": "22.0.6",
"@angular/common": "22.0.8",

11
packages/README.md Normal file
View File

@@ -0,0 +1,11 @@
# packages/
Scaffolding for `@marketplaces/auth` and `@marketplaces/payment` — see [ADR-0001](../docs/context/adrs/ADR-0001-extract-auth-and-payment-into-shared-marketplaces-packages.md).
These are standalone-buildable packages (their own `package.json`/`tsconfig.json`), not yet wired into this repo's Angular build and not yet containing migrated code. They live here temporarily so the structure, versioning, and CI can be reviewed before:
1. a target git repo is created for each package (or one shared repo, per ADR),
2. code moves out of `src/app/core/auth`, `src/app/core/admin-auth`, `src/app/core/finance`, `src/app/core/pricing`,
3. this repo switches to installing them as normal npm dependencies.
See [docs/PACKAGE-EXTRACTION.md](../docs/PACKAGE-EXTRACTION.md) for the full build/version/publish/consume workflow.

View File

@@ -0,0 +1,21 @@
{
"name": "@marketplaces/auth",
"version": "0.1.0",
"description": "Shared customer + admin auth client (VK ID/OTP/session, ed25519 admin verification, guards, interceptors) for marketplaces projects.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "echo \"no tests yet\" && exit 0"
},
"peerDependencies": {
"@angular/core": ">=22.0.0",
"@angular/common": ">=22.0.0",
"rxjs": ">=7.8.0"
},
"publishConfig": {
"access": "restricted"
},
"license": "UNLICENSED"
}

View File

@@ -0,0 +1,7 @@
import { InjectionToken } from '@angular/core';
/** Base URL for the auth backend, e.g. `https://api.example.com`. Provide from the consuming app's environment config. */
export const AUTH_API_URL = new InjectionToken<string>('@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. */
export const TELEGRAM_BOT_USERNAME = new InjectionToken<string>('@marketplaces/auth TELEGRAM_BOT_USERNAME');

View File

@@ -1,22 +1,20 @@
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { AuthChallenge, AuthTokenPair, RefreshTokenRequest, VerifySignatureRequest } from '../models/auth-api.model';
import { AUTH_API_URL } from '../config';
import { AuthChallenge, AuthTokenPair, RefreshTokenRequest, VerifySignatureRequest } from './models/auth-api.model';
/**
* Thin HTTP client for the Ed25519 admin auth endpoints documented in
* docs/AUTH.md. These endpoints do not exist on the backend yet (FUTURE -
* see docs/backend/BACKEND-INTEGRATION.md §2.5) - calling them today 404s
* or connection-errors, which AuthService maps to the
* 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, ready for
* the moment the backend ships.
* here; this is real HttpClient wiring against the real contract.
*/
@Injectable({ providedIn: 'root' })
export class AuthApiService {
private readonly http = inject(HttpClient);
private readonly baseUrl = `${environment.authApiUrl}/api/admin/auth`;
private readonly baseUrl = `${inject(AUTH_API_URL)}/api/admin/auth`;
requestChallenge(): Observable<AuthChallenge> {
return this.http.get<AuthChallenge>(`${this.baseUrl}/challenge`);

View File

@@ -4,7 +4,7 @@ import { finalize } from 'rxjs';
import { AuthService } from './auth.service';
import { PermissionService } from './permission.service';
import { SessionService } from './session.service';
import { Permission } from '../models/permission.model';
import { Permission } from './models/permission.model';
/**
* Public surface for components/pages. Components should depend on this,

View File

@@ -2,8 +2,8 @@ import { Injectable, inject, signal } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, switchMap, tap, throwError } from 'rxjs';
import { Observable } from 'rxjs';
import { AuthTokenPair } from '../models/auth-api.model';
import { AuthError, authErrorCodeFromBackendCode, authErrorCodeFromStatus } from '../models/auth-error.model';
import { AuthTokenPair } from './models/auth-api.model';
import { AuthError, authErrorCodeFromBackendCode, authErrorCodeFromStatus } from './models/auth-error.model';
import { AuthApiService } from './auth-api.service';
import { Ed25519KeypairService } from './ed25519-keypair.service';
import { SessionService } from './session.service';
@@ -18,7 +18,9 @@ export type LoginPhase = 'idle' | 'requesting-challenge' | 'signing' | 'verifyin
* POST /api/admin/auth/verify -> { token, refreshToken }
*
* This is the lowest-level orchestrator; components should go through
* AuthFacade rather than calling this directly.
* AuthFacade rather than calling this directly. Exported from the package
* barrel as `Ed25519AuthService` to avoid colliding with the telegram
* module's `AuthService`.
*/
@Injectable({ providedIn: 'root' })
export class AuthService {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { JwtClaims } from '../models/auth-api.model';
import { JwtClaims } from './models/auth-api.model';
/**
* Client-side JWT *decoding* only - never verification. The signature is

View File

@@ -1,11 +1,6 @@
import { AdminRole } from './permission.model';
/**
* Wire contracts for the Ed25519 challenge/response admin auth flow. These
* are documented in docs/AUTH.md and match the endpoints listed there
* exactly - none of this is invented beyond what's documented as FUTURE
* there and in docs/backend/BACKEND-INTEGRATION.md §2.5.
*/
/** Wire contracts for the Ed25519 challenge/response admin auth flow. */
export interface AuthChallenge {
nonce: string;
/** ISO 8601 issue time of the challenge. */
@@ -33,7 +28,7 @@ export interface RefreshTokenRequest {
* 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 per docs/AUTH.md security considerations.
* server-side.
*/
export interface JwtClaims {
sub: string;

View File

@@ -1,7 +1,7 @@
/**
* Error codes the Ed25519 admin auth flow can surface to the UI. Each maps to
* a dedicated screen (see `core/auth/pages`) rather than a generic toast,
* because the recovery action differs per code (re-login vs. retry vs. wait).
* a dedicated screen rather than a generic toast, because the recovery
* action differs per code (re-login vs. retry vs. wait).
*/
export type AuthErrorCode =
| 'session-expired'
@@ -17,12 +17,7 @@ export interface AuthError {
status?: number;
}
/**
* Maps the backend error envelope's `error.code` (see
* BACKEND-API-REFERENCE.md §5) 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.
*/
/** 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: Record<string, AuthErrorCode> = {
TOKEN_EXPIRED: 'session-expired',
INVALID_SIGNATURE: 'invalid-signature',

View File

@@ -1,17 +1,11 @@
/**
* Roles the Ed25519 JWT `role` claim is expected to carry (see docs/AUTH.md
* §JWT Claims). Ordered highest-to-lowest privilege; PermissionService does
* not rely on the order, it is documentation only.
*/
/** 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. */
export type AdminRole = 'Owner' | 'Administrator' | 'Editor' | 'Support' | 'ReadOnly';
/**
* Coarse-grained permission keys. Intentionally small and domain-agnostic
* (mirrors the existing bootstrap-level `PermissionsConfig` shape in
* `shared/models/config/permissions.model.ts`) - fine-grained, per-domain
* permissions stay server-side until the backend ships a real permission
* model; the frontend only needs enough to hide/disable UI, never to be the
* source of truth for authorization.
* 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.
*/
export type Permission =
| 'backoffice.read'

View File

@@ -1,11 +1,11 @@
import { Injectable, computed, inject } from '@angular/core';
import { Permission, ROLE_PERMISSIONS } from '../models/permission.model';
import { Permission, ROLE_PERMISSIONS } from './models/permission.model';
import { SessionService } from './session.service';
/**
* 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; see docs/AUTH.md security considerations.
* every mutation server-side.
*/
@Injectable({ providedIn: 'root' })
export class PermissionService {

View File

@@ -1,5 +1,5 @@
import { Injectable, computed, signal } from '@angular/core';
import { AuthTokenPair, JwtClaims } from '../models/auth-api.model';
import { AuthTokenPair, JwtClaims } from './models/auth-api.model';
import { JwtService } from './jwt.service';
export type SessionStatus = 'unknown' | 'restoring' | 'authenticated' | 'unauthenticated' | 'expired';
@@ -11,9 +11,9 @@ const REFRESH_SKEW_MS = 60_000;
/**
* Holds the Ed25519-flow JWT/refresh-token pair and derived claims. Separate
* from AdminAuthService (Telegram-session state) by design - the two auth
* mechanisms are not merged until the backend actually ships the Ed25519
* endpoints and a migration decision is made (see docs/AUTH.md).
* 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.
*/
@Injectable({ providedIn: 'root' })
export class SessionService {

View File

@@ -0,0 +1,43 @@
// @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.
export { AUTH_API_URL, TELEGRAM_BOT_USERNAME } from './config';
// Telegram module
export { AuthSession, WebSessionStart, AuthStatus, AdminAuthStatus } from './telegram/models/session.model';
export { TelegramSessionApiService } from './telegram/telegram-session-api.service';
export { AuthService } from './telegram/auth.service';
export { AdminAuthService } from './telegram/admin-auth.service';
export { adminAuthGuard } from './telegram/admin-auth.guard';
export { adminAuthHeadersInterceptor } from './telegram/admin-auth-headers.interceptor';
// Ed25519 module (namespaced re-exports to avoid colliding with the telegram module's AuthService)
export { AuthService as Ed25519AuthService } from './ed25519/auth.service';
export { AuthFacade } from './ed25519/auth-facade.service';
export { AuthApiService } from './ed25519/auth-api.service';
export { SessionService } from './ed25519/session.service';
export { JwtService } from './ed25519/jwt.service';
export { Ed25519KeypairService } from './ed25519/ed25519-keypair.service';
export { PermissionService } from './ed25519/permission.service';
export {
Ed25519VerificationService,
Ed25519Challenge,
Ed25519SignedResponse,
Ed25519VerificationResult
} from './ed25519/ed25519-verification.model';
export { NoopEd25519VerificationService } from './ed25519/noop-ed25519-verification.service';
export {
AuthChallenge,
VerifySignatureRequest,
AuthTokenPair,
RefreshTokenRequest,
JwtClaims
} from './ed25519/models/auth-api.model';
export { AuthErrorCode, AuthError, authErrorCodeFromBackendCode, authErrorCodeFromStatus } from './ed25519/models/auth-error.model';
export { AdminRole, Permission, ROLE_PERMISSIONS } from './ed25519/models/permission.model';
export type { LoginPhase } from './ed25519/auth.service';
export type { SessionStatus } from './ed25519/session.service';

View File

@@ -2,14 +2,13 @@ import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AdminAuthService } from './admin-auth.service';
/** Backend paths that require an active AdminWebSessionID per API-REFERENCE.md §0. */
/** 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. Mirrors
* apiHeadersInterceptor's self-guarding pattern but scoped to admin-gated
* paths so it never touches customer requests and never reads AuthService's
* session.
* 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.
*/
export const adminAuthHeadersInterceptor: HttpInterceptorFn = (req, next) => {
const isAdminRequest = ADMIN_GATED_PATH_SEGMENTS.some(segment => req.url.includes(segment));

View File

@@ -0,0 +1,15 @@
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AdminAuthService } from './admin-auth.service';
/** Guards `/admin/**`-style routes. Never shares state with the customer auth guard/service. */
export const adminAuthGuard: CanActivateFn = () => {
const adminAuth = inject(AdminAuthService);
if (adminAuth.isAuthenticated()) {
return true;
}
adminAuth.requestLogin();
return false;
};

View File

@@ -1,23 +1,21 @@
import { Injectable, signal, computed, inject } from '@angular/core';
import { Injectable, signal, computed, inject, isDevMode } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { AdminAuthStatus } from '../../models/admin-auth.model';
import { AuthSession, WebSessionStart } from '../../models/auth.model';
import { TelegramSessionApiService } from '../../services/telegram-session-api.service';
import { environment } from '../../../environments/environment';
import { AdminAuthStatus, AuthSession, WebSessionStart } from './models/session.model';
import { TelegramSessionApiService } from './telegram-session-api.service';
/**
* Admin login uses the exact same Telegram QR/session API as the customer
* login (TelegramSessionApiService, `{authApiUrl}/users/sessions`) - 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.
* 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.
*
* Backend gap this creates (see docs/backend/BACKEND-INTEGRATION.md §2.5): 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.
* 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';
@@ -93,12 +91,12 @@ export class AdminAuthService {
/**
* 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 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.
* 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 {
if (environment.production) {
if (!isDevMode()) {
return;
}
this.hideLogin();

View File

@@ -1,11 +1,12 @@
import { Injectable, signal, computed, inject } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { AuthSession, AuthStatus, WebSessionStart } from '../models/auth.model';
import { AuthSession, AuthStatus, WebSessionStart } from './models/session.model';
import { TelegramSessionApiService } from './telegram-session-api.service';
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. */
@Injectable({
providedIn: 'root'
})

View File

@@ -13,3 +13,4 @@ export interface WebSessionStart {
}
export type AuthStatus = 'unknown' | 'checking' | 'authenticated' | 'expired' | 'unauthenticated';
export type AdminAuthStatus = 'unknown' | 'checking' | 'authenticated' | 'expired' | 'unauthenticated';

View File

@@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, catchError, map } from 'rxjs';
import { AuthSession, WebSessionStart } from '../models/auth.model';
import { environment } from '../../environments/environment';
import { generateGuid } from '../shared/util/guid.util';
import { AuthSession, WebSessionStart } from './models/session.model';
import { AUTH_API_URL, TELEGRAM_BOT_USERNAME } from '../config';
import { generateGuid } from '../util/guid.util';
const SESSION_MAX_AGE_SECONDS = 60 * 60;
const DEFAULT_TELEGRAM_BOT_USERNAME = 'DexarSupport_bot';
/**
* The one Telegram QR/session API (`{authApiUrl}/users/sessions`). Customer
@@ -17,9 +18,9 @@ const SESSION_MAX_AGE_SECONDS = 60 * 60;
*/
@Injectable({ providedIn: 'root' })
export class TelegramSessionApiService {
private readonly authApiUrl = environment.authApiUrl;
constructor(private readonly http: HttpClient) {}
private readonly http = inject(HttpClient);
private readonly authApiUrl = inject(AUTH_API_URL);
private readonly telegramBotUsername = inject(TELEGRAM_BOT_USERNAME, { optional: true });
createSession(): Observable<WebSessionStart> {
const webSessionID = generateGuid();
@@ -67,7 +68,7 @@ export class TelegramSessionApiService {
}
private getBotUsername(): string {
return (environment as Record<string, unknown>)['telegramBot'] as string || 'DexarSupport_bot';
return this.telegramBotUsername || DEFAULT_TELEGRAM_BOT_USERNAME;
}
private normalizeWebSession(response: Record<string, unknown> | null, fallbackSessionId: string): AuthSession | null {

View File

@@ -0,0 +1,21 @@
/** RFC4122 v4-ish GUID, using crypto when available. Shared by customer and admin session creation. */
export function generateGuid(): string {
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('')}`;
}

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"useDefineForClassFields": false
},
"include": ["src"]
}

View File

@@ -0,0 +1,21 @@
{
"name": "@marketplaces/payment",
"version": "0.1.0",
"description": "Shared payment/finance client (FX, pricing, checkout gateways) for marketplaces projects. Thin by design — payment business logic stays server-side per Phase 1/7 backend contracts.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "echo \"no tests yet\" && exit 0"
},
"peerDependencies": {
"@angular/core": ">=22.0.0",
"@angular/common": ">=22.0.0",
"rxjs": ">=7.8.0"
},
"publishConfig": {
"access": "restricted"
},
"license": "UNLICENSED"
}

View File

@@ -0,0 +1,5 @@
// @marketplaces/payment — public API barrel.
// Scaffold only: code migrates here from src/app/core/finance and src/app/core/pricing
// per ADR-0001 (docs/context/adrs/ADR-0001-extract-auth-and-payment-into-shared-marketplaces-packages.md).
// Nothing is exported yet — this repo still owns the live implementation until migration lands.
export {};

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"useDefineForClassFields": false
},
"include": ["src"]
}

12
renovate.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"packageRules": [
{
"matchPackageNames": ["@marketplaces/auth", "@marketplaces/payment"],
"groupName": "marketplaces shared packages",
"automerge": false,
"labels": ["shared-package-update"]
}
]
}

View File

@@ -7,12 +7,11 @@ import { cacheInterceptor } from './interceptors/cache.interceptor';
import { apiBaseUrlInterceptor } from './interceptors/api-base-url.interceptor';
import { apiHeadersInterceptor } from './interceptors/api-headers.interceptor';
import { mockDataInterceptor } from './interceptors/mock-data.interceptor';
import { adminAuthHeadersInterceptor } from './core/admin-auth/admin-auth-headers.interceptor';
import { Ed25519VerificationService } from './core/admin-auth/ed25519-verification.model';
import { NoopEd25519VerificationService } from './core/admin-auth/noop-ed25519-verification.service';
import { adminAuthHeadersInterceptor, Ed25519VerificationService, NoopEd25519VerificationService, AUTH_API_URL, TELEGRAM_BOT_USERNAME } from '@marketplaces/auth';
import { provideServiceWorker } from '@angular/service-worker';
import { MediaRepository } from './core/media/media-repository';
import { MockMediaRepository } from './core/media/mock-media-repository.service';
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
@@ -25,6 +24,8 @@ export const appConfig: ApplicationConfig = {
provideHttpClient(withXhr(),
withInterceptors([mockDataInterceptor, apiBaseUrlInterceptor, apiHeadersInterceptor, adminAuthHeadersInterceptor, cacheInterceptor])
),
{ provide: AUTH_API_URL, useValue: environment.authApiUrl },
{ provide: TELEGRAM_BOT_USERNAME, useValue: environment.telegramBot },
{ provide: Ed25519VerificationService, useClass: NoopEd25519VerificationService },
{ provide: MediaRepository, useClass: MockMediaRepository },
provideServiceWorker('ngsw-worker.js', {

View File

@@ -1,7 +1,8 @@
import { Routes } from '@angular/router';
import { languageGuard } from './guards/language.guard';
import { projectEditorDirtyGuard } from './features/project-editor/guards/project-editor-dirty.guard';
import { adminAuthGuard, requireAdminPermission } from './core/admin-auth/admin-auth.guard';
import { adminAuthGuard } from '@marketplaces/auth';
import { requireAdminPermission } from './core/admin-auth/admin-auth.guard';
import { authRoutes } from './core/auth/auth.routes';
import { adminCategoryDirtyGuard } from './features/admin/categories/guards/admin-category-dirty.guard';
import { adminProductDirtyGuard } from './features/admin/products/guards/admin-product-dirty.guard';

View File

@@ -16,8 +16,7 @@ import { UiRuntimeFacade } from './facades/runtime/ui-runtime.facade';
import { ApiHealthService } from './services/api-health.service';
import { SeoService } from './services/seo.service';
import { FloatingNotificationsComponent } from './features/website/user-experience/components/floating-notifications/floating-notifications.component';
import { AdminAuthService } from './core/admin-auth/admin-auth.service';
import { AuthService } from './services/auth.service';
import { AdminAuthService, AuthService } from '@marketplaces/auth';
import { TelegramLoginComponent } from './components/telegram-login/telegram-login.component';
@Component({

View File

@@ -6,7 +6,7 @@ import { of } from 'rxjs';
import { BootstrapConfig } from '../../shared/models/config';
import { CONFIG_PROVIDER } from '../../core/config/config-provider.token';
import { ConfigService } from '../../core/config/config.service';
import { AuthService } from '../../services/auth.service';
import { AuthService, AUTH_API_URL } from '@marketplaces/auth';
import { HeaderComponent } from './header.component';
function makeBootstrap(): BootstrapConfig {
@@ -52,6 +52,7 @@ describe('HeaderComponent profile control (login/logout gating regression)', ()
provideHttpClient(),
provideHttpClientTesting(),
{ provide: CONFIG_PROVIDER, useValue: { loadBootstrap: () => of(makeBootstrap()) } },
{ provide: AUTH_API_URL, useValue: 'https://test.local' },
{ provide: AuthService, useValue: fakeAuth },
],
});

View File

@@ -14,7 +14,7 @@ import { FeatureConfigService } from '../../core/config/feature-config.service';
import { DEFAULT_HEADER_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG } from '../../shared/models/config';
import { StaticPageResolverService } from '../../core/config/static-page-resolver.service';
import { IconComponent } from '../../shared/ui/icon/icon.component';
import { AuthService } from '../../services/auth.service';
import { AuthService } from '@marketplaces/auth';
import { TelegramLoginComponent } from '../telegram-login/telegram-login.component';
@Component({

View File

@@ -1,12 +1,10 @@
import { Component, ChangeDetectionStrategy, Input, Injector, Signal, inject, effect, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import { AdminAuthService } from '../../core/admin-auth/admin-auth.service';
import { AuthService, AdminAuthService, AuthSession } from '@marketplaces/auth';
import { LanguageService } from '../../services/language.service';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { QrLoginEngine } from '../../shared/qr-login/qr-login.engine';
import { QrLoginAdapter, QrLoginStatus } from '../../shared/qr-login/qr-login.model';
import { AuthSession } from '../../models/auth.model';
import { IconComponent } from '../../shared/ui/icon/icon.component';
/**

View File

@@ -1,24 +1,14 @@
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AdminAuthService } from './admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
import { AdminPermissionsService } from './admin-permissions.service';
/** Guards `/admin/**` routes. Never shares state with the customer auth guard/service. */
export const adminAuthGuard: CanActivateFn = () => {
const adminAuth = inject(AdminAuthService);
if (adminAuth.isAuthenticated()) {
return true;
}
adminAuth.requestLogin();
return false;
};
/**
* UI-only gate for a specific permission, on top of adminAuthGuard's
* authentication check. See AdminPermissionsService for why this is
* cosmetic until the backend ships real admin-role enforcement.
* UI-only gate for a specific permission, on top of the package's
* adminAuthGuard authentication check. See AdminPermissionsService for why
* this is cosmetic until the backend ships real admin-role enforcement.
* Kept app-local because it depends on AdminPermissionsService, which reads
* this app's mock Users domain - not a portable auth concern.
*/
export function requireAdminPermission(permission: string): CanActivateFn {
return () => {

View File

@@ -1,6 +1,6 @@
import { Injectable, computed, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { AdminAuthService } from './admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
import { AdminUsersLocalGateway } from '../../features/admin/users/services/admin-users-local.gateway';
/**

View File

@@ -1,7 +1,6 @@
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { ButtonComponent } from '../../../shared/ui/button/button.component';
import { AuthFacade } from '../services/auth-facade.service';
import { Ed25519KeypairService } from '../services/ed25519-keypair.service';
import { AuthFacade, Ed25519KeypairService } from '@marketplaces/auth';
/**
* Ed25519 admin login page. Prepared UI for the flow described in

View File

@@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { map } from 'rxjs';
import { ButtonComponent } from '../../../shared/ui/button/button.component';
import { EmptyStateComponent } from '../../../shared/ui/empty-state/empty-state.component';
import { AuthErrorCode } from '../models/auth-error.model';
import { AuthErrorCode } from '@marketplaces/auth';
interface AuthErrorCopy {
title: string;

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { ApiService } from '../../../services';
import { AuthService } from '../../../services/auth.service';
import { AuthService } from '@marketplaces/auth';
import { CategoryService } from '../../categories/category.service';
import { ProductDataProvider } from './product-data-provider.interface';
import { Product, ProductCategory, ProductListQuery, ProductListResult, ProductSearchQuery, RelatedProductsQuery } from '../models/product-domain.model';

View File

@@ -2,7 +2,7 @@ import { Injectable, computed, effect, inject, signal } from '@angular/core';
import { take } from 'rxjs/operators';
import { ProjectEditorFacade } from '../../../project-editor/facade/project-editor.facade';
import { EditorSchemaService } from '../../../project-editor/schema/editor-schema.service';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
import { environment } from '../../../../../environments/environment';
import { ADMIN_DASHBOARD_METRICS_GATEWAY } from '../services/admin-dashboard-metrics-gateway.token';
import { AdminDashboardHistoryService } from '../services/admin-dashboard-history.service';

View File

@@ -3,7 +3,7 @@ import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AdminOrder, AdminOrderListFilters, AdminOrdersListResult, AdminOrderStatus, TERMINAL_ORDER_STATUSES } from '../models/admin-order.model';
import { AdminOrdersGateway } from './admin-orders-gateway.interface';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
const STATUSES: AdminOrderStatus[] = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'];
const CUSTOMER_NAMES = ['Anna Petrova', 'Karen Sargsyan', 'Ivan Ivanov', 'Mariam Grigoryan', 'Sergey Volkov', 'Lilit Hakobyan'];

View File

@@ -1,6 +1,7 @@
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { signal } from '@angular/core';
import { AUTH_API_URL } from '@marketplaces/auth';
import { AdminLayoutComponent } from './admin-layout.component';
import { AdminOrderWatcherService } from './services/admin-order-watcher.service';
import { AdminOrder } from '../orders/models/admin-order.model';
@@ -47,6 +48,7 @@ describe('AdminLayoutComponent notifications bell', () => {
imports: [AdminLayoutComponent],
providers: [
provideRouter([]),
{ provide: AUTH_API_URL, useValue: 'https://test.local' },
{ provide: AdminOrderWatcherService, useValue: watcherStub },
],
});

View File

@@ -6,7 +6,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { TranslatePipe } from '../../../i18n/translate.pipe';
import { TranslateService } from '../../../i18n/translate.service';
import { LanguageService } from '../../../services/language.service';
import { AdminAuthService } from '../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
import { ADMIN_NAV_BOTTOM, ADMIN_NAV_PRIMARY, AdminBreadcrumbEntry, AdminNavEntry } from './admin-nav.model';
import { IconComponent } from '../../../shared/ui/icon/icon.component';
import { AdminPreferencesService } from '../settings/services/admin-preferences.service';

View File

@@ -6,7 +6,7 @@ import { AdminOrderWatcherService } from './admin-order-watcher.service';
import { AdminOrdersLocalGateway } from '../../orders/services/admin-orders-local.gateway';
import { AdminOrder, AdminOrdersListResult } from '../../orders/models/admin-order.model';
import { UserNotificationService } from '../../../website/user-experience/services/user-notification.service';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
function makeOrder(id: string, orderNumber: string, createdAt: string): AdminOrder {
return {

View File

@@ -5,7 +5,7 @@ import { LocalStorageService } from '../../../../core/storage/local-storage.serv
import { UserNotificationService } from '../../../website/user-experience/services/user-notification.service';
import { LanguageService } from '../../../../services/language.service';
import { TranslateService } from '../../../../i18n/translate.service';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
const LAST_NOTIFIED_KEY = 'adminOrderWatcher.lastNotifiedOrderId.v1';
const LAST_NOTIFIED_AT_KEY = 'adminOrderWatcher.lastNotifiedOrderCreatedAt.v1';

View File

@@ -4,7 +4,7 @@ import { delay } from 'rxjs/operators';
import { AdminTransaction, AdminTransactionListFilters, AdminTransactionsListResult } from '../models/admin-transaction.model';
import { AdminTransactionsGateway } from './admin-transactions-gateway.interface';
import { AdminOrdersLocalGateway } from '../../orders/services/admin-orders-local.gateway';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
const METHODS = ['card', 'qr', 'cash_on_delivery'];

View File

@@ -3,7 +3,7 @@ import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AdminInvitation, AdminUserRoleRecord, AdminSession, AdminUser, AdminUserAuditEntry, AdminUserScope, AdminUserStatus } from '../models/admin-user.model';
import { AdminUsersGateway } from './admin-users-gateway.interface';
import { AdminAuthService } from '../../../../core/admin-auth/admin-auth.service';
import { AdminAuthService } from '@marketplaces/auth';
const BUILT_IN_ROLES: AdminUserRoleRecord[] = [
{ id: 'owner', name: 'Owner', permissions: ['*'], builtIn: true },

View File

@@ -1,5 +1,5 @@
import { Injectable, inject } from '@angular/core';
import { AuthService } from '../../../services/auth.service';
import { AuthService } from '@marketplaces/auth';
import { SearchHistory } from '../models/search.model';
import { BackendSearchHistoryRepository, LocalSearchHistoryRepository, SearchHistoryRepository } from './search-history.repository';

View File

@@ -21,7 +21,7 @@ import { AnalyticsService } from '../../../../core/analytics/services/analytics.
import { ApiService } from '../../../../services/api.service';
import { LocalStorageService } from '../../../../core/storage/local-storage.service';
import { UserNotificationService } from '../../user-experience/services/user-notification.service';
import { AuthService } from '../../../../services/auth.service';
import { AuthService } from '@marketplaces/auth';
const RESTOCK_SUBSCRIPTIONS_KEY = 'restockSubscriptions';
import { ProductDeliveryInformationComponent } from '../components/delivery-information/delivery-information.component';

View File

@@ -3,7 +3,7 @@ import { inject } from '@angular/core';
import { ApiConfigService } from '../core/config/api-config.service';
import { LocationService } from '../services/location.service';
import { LanguageService } from '../services/language.service';
import { AuthService } from '../services/auth.service';
import { AuthService } from '@marketplaces/auth';
/** Map internal language codes to API header values */
const LANG_HEADER_MAP: Record<string, string> = {

View File

@@ -9,7 +9,7 @@ import { ResolvedWidget } from '../../widgets/contracts/widget-component.contrac
import { WidgetHostService } from '../../dynamic-renderer/widget-host/widget-host.service';
import { LanguageService } from '../../services/language.service';
import { Category } from '../../core/categories/models/category-domain.model';
import { AuthService } from '../../services/auth.service';
import { AuthService } from '@marketplaces/auth';
@Component({
selector: 'app-dynamic-page-layout',

View File

@@ -1 +0,0 @@
export type AdminAuthStatus = 'unknown' | 'checking' | 'authenticated' | 'expired' | 'unauthenticated';

View File

@@ -1,4 +1,3 @@
export * from './category.model';
export * from './item.model';
export * from './location.model';
export * from './auth.model';

View File

@@ -3,7 +3,8 @@ import { DecimalPipe } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { CartService, ApiService, LanguageService, AuthService } from '../../services';
import { CartService, ApiService, LanguageService } from '../../services';
import { AuthService } from '@marketplaces/auth';
import { Item, CartItem, DeliveryOption } from '../../models';
import { EMPTY, interval, of, Subscription } from 'rxjs';
import { catchError, exhaustMap, take, timeout } from 'rxjs/operators';

View File

@@ -3,4 +3,3 @@ export * from './cart.service';
export * from './language.service';
export * from './seo.service';
export * from './location.service';
export * from './auth.service';