lat and long
This commit is contained in:
@@ -48,10 +48,24 @@ export function resolveRole(user: Pick<FiestaUser, 'roleid' | 'issuperadmin'>):
|
||||
|
||||
export function toSessionUser(user: FiestaUser): SessionUser {
|
||||
const name = [user.firstname, user.lastname].filter(Boolean).join(' ').trim();
|
||||
|
||||
/**
|
||||
* Trimmed, and that is load-bearing.
|
||||
*
|
||||
* `fullname` is not a column — `GetTenantUserById` builds it as
|
||||
* `concat(a.firstname,' ',a.lastname)` (`userRepository.go:249`). For an
|
||||
* account with neither name filled in, that concat produces a SINGLE SPACE,
|
||||
* not an empty string, and a single space is truthy. So the fallback chain
|
||||
* below stopped here and handed the app a name of " ", which then rendered as
|
||||
* an avatar with no initials in it. Trimming lets it fall through to the
|
||||
* email, which every account has.
|
||||
*/
|
||||
const fullname = (user.fullname ?? '').trim();
|
||||
|
||||
return {
|
||||
userid: user.userid,
|
||||
role: resolveRole(user),
|
||||
name: name || user.fullname || user.authname || user.email,
|
||||
name: name || fullname || user.authname || user.email,
|
||||
email: user.email,
|
||||
roleid: user.roleid,
|
||||
tenantid: user.tenantid,
|
||||
|
||||
@@ -77,6 +77,60 @@ export async function login(email: string, password: string): Promise<SessionUse
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the next step is for this email — before anyone types a password.
|
||||
*
|
||||
* `'setup'` means the account exists and has never had a password; `'password'`
|
||||
* means it has one. Anything else throws with a message worth showing.
|
||||
*
|
||||
* ── Why probe at all ─────────────────────────────────────────────────────────
|
||||
*
|
||||
* A tenant created by `createtenantuser`, and every branch created by
|
||||
* `createtenantlocation`, is spawned with an EMPTY password. Their owner's
|
||||
* first sign-in therefore cannot succeed, and asking them for a password first
|
||||
* asks for something that does not exist — they type a guess, watch it fail,
|
||||
* and only then are told to invent one. The old console avoids that by checking
|
||||
* the email before the password field is ever shown, and it is right to.
|
||||
*
|
||||
* ── How one endpoint answers two questions ───────────────────────────────────
|
||||
*
|
||||
* There is no lookup endpoint. This posts to `applogin` with no password at
|
||||
* all, which `userService.go:64-123` answers in four distinguishable ways:
|
||||
*
|
||||
* 409 + status false → no such account ("Invalid Email")
|
||||
* 403 → account deactivated
|
||||
* 409 + status true → exists, no password set (carries the userid)
|
||||
* 401 + status true → exists, has a password ("Password is required")
|
||||
*
|
||||
* The last one is the whole trick: a password-less attempt against a real
|
||||
* account is refused with a DIFFERENT code than a wrong password, so existence
|
||||
* can be established without guessing at one.
|
||||
*
|
||||
* This does tell an anonymous caller whether an email has an account here. That
|
||||
* is a real disclosure and worth naming — but the login already answers
|
||||
* "Invalid Email" versus "Incorrect password" to any caller who sends a wrong
|
||||
* password, so the probe reveals nothing that was not already available with
|
||||
* one more field filled in.
|
||||
*/
|
||||
export type AccountCheck = { state: 'password' } | { state: 'setup'; userid: number };
|
||||
|
||||
export async function checkAccount(email: string): Promise<AccountCheck> {
|
||||
const envelope = await api.envelope<{ setup?: boolean; userid?: number }>(
|
||||
`${WEB}/users/applogin`,
|
||||
{ method: 'POST', body: { authname: email.trim(), configid: CONFIG_ID } },
|
||||
);
|
||||
|
||||
if (envelope.code === 409 && envelope.details?.setup === true) {
|
||||
return { state: 'setup', userid: envelope.details.userid ?? 0 };
|
||||
}
|
||||
// "Password is required" — the account is real and has one. Exactly what we
|
||||
// wanted to learn, arriving as a refusal.
|
||||
if (envelope.code === 401) {
|
||||
return { state: 'password' };
|
||||
}
|
||||
throw new Error(loginMessage(envelope.code, envelope.message));
|
||||
}
|
||||
|
||||
/** The backend's floor, enforced here too so the refusal is instant. */
|
||||
export const MIN_PASSWORD_LENGTH = 6;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user