| type | Domain Concepts | |||||
|---|---|---|---|---|---|---|
| title | Domain Concepts | |||||
| description | RESTHeart Cloud auth model, teams, invitations, tokens, feature flags, and the SSR/CSR split. | |||||
| tags |
|
|||||
| resource | /src/environments/environment.ts |
The starter is a frontend for RESTHeart Cloud, a hosted backend service. The auth model:
- Users are identified by email (
user._idis the email address) - Profile data lives at
user.profile.name/user.profile.surname - Teams are multi-tenant containers. Each user belongs to one or more teams.
- Roles per team:
ownerormember. Owners can invite, remove, and manage team settings. - Active team — the JWT contains a
teamclaim ({ _id, role }).switchTeam()reissues the JWT with a different active team.
RESTHeart Cloud uses bearer token authentication:
- Token is obtained via
POST /token(login) or returned directly by activate/reset-password/switch-team endpoints (?delivery=body) - Token has a 15-minute TTL
scheduleRefresh()silently renews at ~80% (~12 minutes) viaGET /token?renew- The HTTP interceptor from
kit-ngattaches the token to all API calls - On 401/expiry, the interceptor clears the session — next
checkSession()returns null
Bearer mode: the starter uses bearer tokens (not cookies). This means:
- Token lives in localStorage (via kit)
- SSR cannot access the token — authenticated routes must be client-rendered
- Token is passed in the
Authorization: Bearer ...header
A team has:
id.$oid— MongoDB ObjectIdname— human-readable namedescription— optionalrole— the current user's role (owner|member)active— whether this is the user's currently active team
Team creation: when a user signs up, a team is auto-created with "{firstName}'s Team" as the name. Additional teams can be created via POST /auth/teams.
Team deletion: only possible when no other members remain. Backend enforces with an atomic findOneAndDelete + $expr size guard — returns 409 if other members exist.
The invitation flow has three paths through a single page (/invitations/accept):
| Scenario | isNewUser |
Auth state | Action |
|---|---|---|---|
| New user | true |
Not logged in | Set password → activate |
| Existing user, logged out | false |
Not logged in | Login → acceptInvite |
| Existing user, logged in | false |
Logged in | acceptInvite directly |
Invitations carry email + token as query params. The token is validated by getInvitation().
Resend cooldown: 5 minutes. Implemented with a reactive now signal ticking every 30s to keep the countdown accurate in zoneless Angular.
Defined in src/environments/environment*.ts:
features: {
emailRegistration: boolean, // Registration & Verification
passwordReset: boolean, // Password Reset
oauthLogin: boolean, // OAuth Social Login
oauthProviders: string[], // e.g. ['google', 'github']
teamInvitations: boolean, // Team Invitations
}How they work:
- Routes are conditionally included in
app.routes.tsbased on flags - UI elements (links, buttons) check flags to decide visibility
- A flag that's off removes both the route AND the UI that links to it
- Flags must match your RESTHeart Cloud service's Sign-up Mgmt → Features toggles — a mismatch causes 403 errors
Default configs:
environment.ts(production): all on exceptoauthLoginenvironment.dev.ts(development): all on includingoauthLoginwith Google
The render boundary is the authentication state:
| Zone | Render mode | Why |
|---|---|---|
| Auth pages (login, signup, verify, forgot/reset password) | Prerender or Client | Static forms can be prerendered; verify/invitations need live API |
| Authenticated shell + inner pages | Client only | Token is in-memory signal, not available during SSR |
This means:
- Auth pages have fast initial paint (prerendered HTML)
- The authenticated shell renders only after CSR hydration
- There's a brief flash between SSR and CSR for auth pages (the prerendered HTML is replaced by client-rendered HTML)
The justSignedUp signal (in just-signed-up.ts) is:
- Set to
truebyApp.consumeFragmentToken()when?flow=signupis in the URL - Read once by
Shellto show the welcome banner - Reset to
falsebyShell's constructor - Never persisted — can't reappear on later logins
Critical: the banner copy says "your account is ready" — it must NOT claim "email verified" because OAuth signups also trigger it (no email verification happened).
src/styles.css section 1 defines CSS custom properties:
- Colour:
--color-bg,--color-surface,--color-primary(RESTHeart amber),--color-link(teal),--color-error - Typography:
--font-family(system-ui),--font-mono(for chrome labels), scale from--text-xsto--text-2xl - Space:
--space-1through--space-8 - Shape:
--radius-sm,--radius,--radius-lg,--border-width
Dark mode overrides these tokens under html.dark. The ThemeService toggles the .dark class on <html> and persists to localStorage['rh-theme'].
- Start with:
@restheart-cloud/kitfor core auth logic - Check:
src/app/pages/auth/for UI implementations - Test with: Manual flows from TEST-CASES.md
- Validation: Auth flows work correctly, no 403 errors
- Start with:
@restheart-cloud/kitfor team functions - Check:
src/app/pages/teams/for team management UI - Test with: Manual flows from TEST-CASES.md
- Validation: Team switching works, team list updates correctly
- Start with:
src/environments/environment.tsandsrc/environments/environment.dev.ts - Check:
src/app/app.routes.tsfor route gating - Test with:
ng serveand verify routes/UI appear/disappear - Validation: Flags match your RESTHeart Cloud service's Sign-up Mgmt → Features toggles
- Start with:
@restheart-cloud/kitfor token management - Check:
src/app/app.tsfor fragment token handling - Test with: Manual flows from TEST-CASES.md
- Validation: Token refresh works at ~80% of 15-minute TTL