store user login
This commit is contained in:
@@ -84,5 +84,8 @@ export const HOME_ROUTE: Record<ConsoleRole, string> = {
|
||||
export const ROLE_LABEL: Record<ConsoleRole, string> = {
|
||||
'nearle-admin': 'Nearle Admin',
|
||||
'store-admin': 'Store Admin',
|
||||
'store-manager': 'Store Manager',
|
||||
// The backend's own word for it: an `app_users` row with roleid 0, bound to
|
||||
// one `tenantlocations.locationid`. "Manager" implied a rank the record does
|
||||
// not carry.
|
||||
'store-manager': 'Store user',
|
||||
};
|
||||
|
||||
@@ -26,31 +26,50 @@ export class PasswordSetupRequiredError extends Error {
|
||||
interface LoginBody {
|
||||
authname: string;
|
||||
password: string;
|
||||
roleid?: number;
|
||||
configid?: number;
|
||||
/**
|
||||
* Not optional in practice.
|
||||
*
|
||||
* The lookup behind every login is `WHERE authname = ? AND configid = ?`
|
||||
* (`userRepository.go:218`). Omitted, Go receives 0, the query matches no
|
||||
* row, and every account on the platform answers "Invalid Email". The console
|
||||
* surface is configid 1 — `createtenantuser` hard-codes it into the account it
|
||||
* spawns for exactly this reason.
|
||||
*/
|
||||
configid: number;
|
||||
}
|
||||
|
||||
/** The console's surface. Every account this app can sign in carries it. */
|
||||
const CONFIG_ID = 1;
|
||||
|
||||
/**
|
||||
* Signs in against the web login endpoint.
|
||||
* Signs in.
|
||||
*
|
||||
* The handler answers HTTP 200 with `status: false` for a wrong password, and
|
||||
* with `code: 409` plus `details.setup` when no password has been set, so the
|
||||
* envelope is inspected rather than trusting the HTTP status.
|
||||
* `applogin`, not `tenant/weblogin`. The latter carries a check the former does
|
||||
* not — `request.roleid == app_users.roleid` (`userService.go:224`) — and since
|
||||
* Go's zero value is 0, a request without a roleid means "roleid must be 0".
|
||||
* That is the branch-user role, so weblogin silently locked out every Store
|
||||
* Admin and every platform operator with a 403 reading "Unauthorized email".
|
||||
*
|
||||
* The handler answers HTTP 200 with `status: false` for most failures, so the
|
||||
* envelope is inspected rather than the HTTP status.
|
||||
*/
|
||||
export async function login(email: string, password: string): Promise<SessionUser> {
|
||||
const body: LoginBody = { authname: email.trim(), password };
|
||||
const body: LoginBody = { authname: email.trim(), password, configid: CONFIG_ID };
|
||||
|
||||
const envelope = await api.envelope<FiestaUser & { setup?: boolean; userid?: number }>(
|
||||
`${WEB}/users/tenant/weblogin`,
|
||||
`${WEB}/users/applogin`,
|
||||
{ method: 'POST', body },
|
||||
);
|
||||
|
||||
// A brand-new account — `createtenantlocation` spawns branch logins with an
|
||||
// empty password — answers `status: true` with a 409 and the userid to set
|
||||
// one against. It is not a failure, it is the first step.
|
||||
if (envelope.code === 409 && envelope.details?.setup === true) {
|
||||
throw new PasswordSetupRequiredError(envelope.details.userid ?? 0);
|
||||
}
|
||||
|
||||
if (envelope.status !== true || !envelope.details) {
|
||||
throw new Error(envelope.message ?? 'Sign-in failed');
|
||||
throw new Error(loginMessage(envelope.code, envelope.message));
|
||||
}
|
||||
|
||||
const session = toSessionUser(envelope.details);
|
||||
@@ -58,6 +77,29 @@ export async function login(email: string, password: string): Promise<SessionUse
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* The backend's own words, where they are usable, and ours where they are not.
|
||||
*
|
||||
* "Invalid Email" is technically true and unhelpful — the same answer covers a
|
||||
* typo and a till account, because roleids 7 and 8 are excluded from every web
|
||||
* login lookup. A cashier is not refused here, they are not found, so the copy
|
||||
* must not say "wrong password".
|
||||
*/
|
||||
function loginMessage(code: number | undefined, message: string | undefined): string {
|
||||
if (code === 409) {
|
||||
return 'We do not recognise that email. Till accounts (supervisor or cashier) sign in at the terminal, not here.';
|
||||
}
|
||||
if (code === 401) {
|
||||
return message?.toLowerCase().includes('required')
|
||||
? 'Enter your password.'
|
||||
: 'That password is not right.';
|
||||
}
|
||||
// 403 covers both an inactive account and an inactive store, and the two
|
||||
// messages differ — pass the backend's through rather than flattening them.
|
||||
if (code === 403) return message ?? 'This account cannot sign in. Contact your administrator.';
|
||||
return message ?? 'Sign-in failed';
|
||||
}
|
||||
|
||||
export function persist(session: SessionUser): void {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(session));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user