Authentication

The authentication subsystem lives entirely in src/app/core/auth/ and is registered by AuthModule.forRoot().

Login pipeline

LoginComponent submits { email, password }
  → reCAPTCHA v3 executes and yields a token
  → AuthService.login(email, password, recaptchaToken)
        GET {authUrl}/token?timezon=<IANA tz>
        headers: Authorization: Basic base64(email:password)
                 recaptcha:     <token>
     ← { token }
  → dispatch login({ authToken })
  → AuthEffects.login$      : localStorage[authTokenKey] = authToken
                              → userRequested()
  → AuthEffects.loadUser$   : AuthService.getUserByToken()
                                 GET supplier details  (SupplierDto)
                                 GET supplier status   (SupplierStatusDto)
                              → EventLoggerService.identify(user)
                              → userLoaded({ user })
                              → userPermissionsRequested()
                              → userDashboardStatusRequested({ user })
                              → loadBalance()
  → AuthEffects.loadedUserPermissions$
                            : PermissionsService.getUserPermissions(user)
                              → userPermissionsLoaded({ permissions })
  → GlobalSubscriptionsService mirrors permissions into NgxPermissionsService
  → Router activates the requested route

The timezon query parameter is spelled that way in the API contract. The value comes from Intl.DateTimeFormat().resolvedOptions().timeZone, captured in the AuthService constructor.

Session restoration

AuthEffects.init$ runs once at startup:

init$ = createEffect(() =>
    defer(() => {
        const authToken = localStorage.getItem(this.env.authTokenKey);
        return authToken ? of(AuthActions.login({ authToken })) : of(noAction());
    }),
);

A stored token re-enters the same pipeline as a fresh login, so user data and permissions are always recomputed rather than trusted from persisted state.

Logout

logout$ = createEffect(() =>
    this.actions$.pipe(
        ofType(AuthActions.logout),
        tap(() => {
            this.stateManager.clear();                        (1)
            localStorage.removeItem(this.env.authTokenKey);   (2)
            setTimeout(() => location.reload(), 1000);        (3)
        }),
    ),
    { dispatch: false },
);
1 PersistentStateManager.clear() wipes persisted slices.
2 The session token is removed.
3 A full page reload after 1 s guarantees no in-memory state survives — cheaper and safer than resetting every slice by hand.

AuthService

src/app/core/auth/_services/auth.service.ts

Session

Method Behaviour

login(email, password, recaptchaToken)

GET {authUrl}/token with Basic auth and a recaptcha header. Returns { token }.

logout()

POST {authUrl}/logout.

getAccessToken()

Observable<string> of localStorage[authTokenKey].

setAccessToken(token) / removeAccessToken()

Write / delete the token.

getHeaders(token)

The header set applied to every authenticated request — see HTTP Interceptors.

User loading and mapping

Method Behaviour

getUserByToken()

SuppliersService.getSupplierDetails() → prepareUser(), then getSupplierStatus(user.id) and copies every matching key onto user.status.

prepareUser(supplier, token)

Maps SupplierDto → User, including nested personal and business information.

prepareBusinessInformation(), preparePersonalInfo(), prepareAddress(), prepareResidentialAddress()

DTO → model mappers for the sub-objects.

prepareUser sets user.address = residentialAddress, duplicating user._personalInformation.residentialAddress. The flat address, firstName, lastName, fullname, companyName, phone and occupation fields on User are marked @Deprecated — read the _personalInformation / _businessInformation objects in new code.

Registration and password reset

Method Endpoint

register(user)

SuppliersService.createSupplierAccount({ firstName, lastName, email, phoneNumber, password, companyName })

requestPassword(email, recaptcha)

POST {apiUrl}/start_password_reset with a recaptcha header. Errors resolve to { error: AuthErrorsEnum.EmailNotFound }.

confirmPasswordReset(verificationCode)

POST {apiUrl}/confirm_password_reset/{code}. Errors resolve to { error: AuthErrorsEnum.InvalidVerificationCode }.

executePasswordChange(newPassword, authToken?)

POST {apiUrl}/execute_password_change. When authToken is supplied it is sent as both X-Session-Token and Authorization: Basic. Errors resolve to { error: AuthErrorsEnum.Fail }.

AuthService.handleError() swallows HTTP failures, logs to the console, and emits a sentinel result object. Callers must inspect response.error rather than relying on the observable erroring.

Legacy surface

getAllUsers, getUserById, createUser, updateUser, deleteUser, findUsers and the whole Role CRUD block target v1/sup_widget and v1/roles. They are inherited from the original admin template and are not used by supplier-facing flows. getAllRoles() returns an empty BehaviorSubject.

Password reset flow

1. /auth/reset-password  → requestPassword(email, recaptcha)
                           POST /start_password_reset
2. Supplier receives an email with a link carrying ?r=<verificationCode>
3. /auth/new-password?r=…  → NewPasswordTokenGuard
                             POST /confirm_password_reset/<code>
                             invalid → warning banner + redirect to /auth/login
4. NewPasswordComponent submits → dispatch newPasswordRequest({ password, verificationCode })
5. AuthEffects.newUserPasswordRequest$
       confirmPasswordReset(code)          → { token }
       executePasswordChange(password, token)
       → newPasswordRequestSuccess | newPasswordRequestError

Impersonation — login as supplier

/auth/auth renders LoginAsSupplierComponent. Internal staff arrive with a pre-issued session token, which is written straight to localStorage and the standard userRequested pipeline takes over. The supplier sees the identical application; permissions are calculated from their real user object.

Auth models

Model Contents

User

Identity, schema, product availability flags, status, dashboardStatus, cancelationStatus, permissions.

Status

~40 supplier state flags returned by getSupplierStatus. See Domain Model.

SupplierDashboardStatus

{ status, enableRejectedNotification }.

PersonalInformation

Name, DOB, phone, email, tax id, residential address.

BusinessInformation

Legal name, business type, incorporation state, owner SSN, EIN, start date, business address.

Address

addressLine1/2, city, state, postCode, country.

Permission

{ id, name, level, parentId, children } — the hierarchical permission node.

Role

Legacy, unused in supplier flows.

Notifications / DashboardNotifications

Dashboard notification payloads.

SocialNetworks

Legacy template model.

Field-level detail: Model Reference.