The Verdaccio registry introduced earlier is unreachable from CI (listens on 127.0.0.1:4873 behind a firewall allowing only 80/443/SSH), which broke the architecture-governance workflow - its npm ci step could no longer resolve @marketplaces/auth. Packages are now published to git release branches (release/auth, release/payment in vitanovaPackages) whose root is the package itself, and installed with git+<repo>#release/auth. No registry, token, tunnel, or CI secret - anonymous git read is enough. - package.json: git dependency; .npmrc removed (no scope mapping needed) - vitanovaPackages release.yml rebuilt to force-push release branches - ADR-0001 amended with the distribution change and why the registry lost - BACKEND-HANDOFF: added the multi-tenancy section (hostname -> tenantKey -> per-tenant bootstrap config), corrected the install and deploy notes, and recorded that no CD pipeline exists - PACKAGE-EXTRACTION / PACKAGES-USAGE rewritten for the git-branch flow Verified: npm ci, arch:check:boundaries, ng build, 103/103 tests, all with no credentials configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.0 KiB
Using @marketplaces/auth and @marketplaces/payment
How to install and consume the shared packages in marketplaces or any other project. For why they exist see ADR-0001; for how they are built and released see PACKAGE-EXTRACTION.md.
1. Install
Nothing to set up. The packages are installed straight over git from release branches in vitanovaPackages, where the repo root is the package:
"@marketplaces/auth": "git+https://sources.vitanova.network/sdarbinyan/vitanovaPackages.git#release/auth"
That is already in marketplaces' package.json, so a fresh clone plus npm install just works — no npm registry, no auth token, no SSH tunnel, no CI secret. Anonymous git read is the only requirement.
To add it to another project:
npm install "git+https://sources.vitanova.network/sdarbinyan/vitanovaPackages.git#release/auth"
On pinning: a branch ref tracks the tip, so npm install can pick up a new build. That is deliberate while the package churns. For reproducible installs, replace #release/auth with a commit SHA. See ADR-0001 on blast radius.
2. Required providers
@marketplaces/auth has no knowledge of any specific app's environment config. It reads two injection tokens, both provided by the consuming app in app.config.ts:
import { AUTH_API_URL, TELEGRAM_BOT_USERNAME } from '@marketplaces/auth';
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: AUTH_API_URL, useValue: environment.authApiUrl },
{ provide: TELEGRAM_BOT_USERNAME, useValue: environment.telegramBot },
// ...
]
};
| Token | Required | Meaning |
|---|---|---|
AUTH_API_URL |
yes | Base URL of the auth backend, e.g. https://api.example.com. Both auth mechanisms build their endpoints from this. |
TELEGRAM_BOT_USERNAME |
no | Bot username for QR/deep-link login URLs. Falls back to a default if absent. |
Missing AUTH_API_URL produces NG0201: No provider found for InjectionToken @marketplaces/auth AUTH_API_URL at the first injection — including in unit tests, where any TestBed that constructs a component touching auth must provide it:
TestBed.configureTestingModule({
providers: [{ provide: AUTH_API_URL, useValue: 'https://test.local' }],
});
3. What is in the package
Two independent auth mechanisms. They deliberately share no state — a customer QR scan never authenticates an admin session or vice versa (distinct cookies, signals, guards, interceptors).
telegram/ — live today
Telegram QR/session auth against {AUTH_API_URL}/users/sessions. One backend endpoint set, used by both customer and admin login; only storage differs.
| Export | What it is |
|---|---|
AuthService |
Customer session. Signals: session, status, isAuthenticated, showLoginDialog, displayName. Methods: checkSession(), createWebSession(), requestLogin(), hideLogin(), logout(), onTelegramLoginComplete(), getTelegramAppLoginUrl(). Cookie webSessionID, SameSite=Lax. |
AdminAuthService |
Admin session. Same signal/method shape plus getAdminToken()/setAdminTokens()/clearAdminTokens() (reserved for when the backend issues admin JWTs) and devBypassLogin() (no-ops outside dev mode). Cookie adminSessionID, SameSite=Strict. |
TelegramSessionApiService |
Thin HTTP client + response normalization. Holds no state, writes no cookies. |
adminAuthGuard |
CanActivateFn — allows if the admin session is authenticated, otherwise opens the login dialog. |
adminAuthHeadersInterceptor |
Attaches AdminWebSessionID (and Authorization: Bearer when a token exists) to admin-gated paths only (/admin/, /backoffice/, /builder/, /media/). Never touches customer requests. |
AuthSession, WebSessionStart, AuthStatus, AdminAuthStatus |
Wire/state types. |
Typical usage:
import { AuthService, AdminAuthService, adminAuthGuard, adminAuthHeadersInterceptor } from '@marketplaces/auth';
// routes
{ path: 'backoffice', canActivate: [adminAuthGuard], loadComponent: ... }
// http
provideHttpClient(withInterceptors([adminAuthHeadersInterceptor, ...]))
// component
private readonly auth = inject(AuthService);
readonly isLoggedIn = this.auth.isAuthenticated; // signal
Security note: the Telegram session API has no concept of "admin." The frontend cannot distinguish an admin Telegram session from a regular one — it only decides where to store the result. Real admin authorization must be enforced server-side on every admin request. See TRACK-S.
ed25519/ — prepared, backend not shipped
Challenge/response admin auth: GET /api/admin/auth/challenge → sign nonce with a device-local non-extractable Ed25519 key → POST /api/admin/auth/verify → JWT pair. Calling these today 404s/connection-errors, which surfaces as the backend-unavailable error screen. Nothing is mocked.
| Export | What it is |
|---|---|
AuthFacade |
The surface components should use. isAuthenticated, status, role, loginPhase, lastError; login(redirectTo?), logout(redirectTo?), restoreSession(), can(permission). |
Ed25519AuthService |
Low-level flow orchestrator (exported under this name so it doesn't collide with the telegram AuthService). |
SessionService |
JWT/refresh pair + derived claims, auto-refresh before expiry. |
Ed25519KeypairService |
WebCrypto Ed25519 keypair in IndexedDB. Private key is non-extractable and never leaves the device. |
PermissionService |
Derives permissions from the JWT role claim. UI-only gate. |
JwtService |
Decode only, never verification — the frontend has no trusted key; signature checking is the backend's job on every request. |
Ed25519VerificationService / NoopEd25519VerificationService |
Abstract seam + fail-closed default binding. |
AdminRole, Permission, ROLE_PERMISSIONS, AuthChallenge, AuthTokenPair, JwtClaims, AuthError, AuthErrorCode, … |
Types and wire contracts. |
Bind the verification seam in app.config.ts:
{ provide: Ed25519VerificationService, useClass: NoopEd25519VerificationService },
4. What deliberately stayed in the app
AdminPermissionsService and requireAdminPermission live in marketplaces (src/app/core/admin-auth/), not in the package. They read this app's mock Users domain to derive a permission set — app-specific, not a portable auth concern. If another project needs permission gating it should use the package's PermissionService (JWT-claim-driven) instead.
5. @marketplaces/payment
Published at 0.1.0 but scaffold only — no implementation yet, nothing exported, and marketplaces does not depend on it. core/finance and core/pricing still live in the app. Payment business logic is server-side by design (see Phase 1 and Phase 7); the eventual package is a thin client for FX/pricing/checkout gateways.
6. Making a change to a package
- Clone vitanovaPackages.
- Edit under
packages/auth/src(orpackages/payment/src), export fromindex.ts. npx changesetat the repo root — pick the package and bump type, write one line about the change.- Commit, push, open a PR to
main. CI builds, tests, and rejects the PR if the changeset is missing. - On merge, CI rebuilds and force-pushes
release/auth/release/payment, and opens a "Version Packages" PR if there are unreleased changesets. - In
marketplaces, runnpm update @marketplaces/auth, then the build + test suite before merging.
Never commit to a release/* branch — they are generated and force-pushed. Never edit node_modules/@marketplaces/* — overwritten on every install.