mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(auth): add login audience middleware (EDRFREIGHT-415)
This commit is contained in:
@@ -15,6 +15,7 @@ describe("freight-api: health + auth contract", () => {
|
||||
method: "POST",
|
||||
url: `${api()}/api/auth/login`,
|
||||
body: { email: "nobody@edr.local", password: "wrong-password" },
|
||||
headers: { "x-client-app": "backoffice" },
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.its("status")
|
||||
@@ -50,8 +51,8 @@ describe("freight-api: health + auth contract", () => {
|
||||
|
||||
it("demo portal users are seeded", () => {
|
||||
// DemoUsersSeeder hardcodes this password (staff users use DEFAULT_PASSWORD)
|
||||
cy.apiLogin("user@gmail.com", Cypress.env("demoPassword"));
|
||||
cy.apiLogin("user2@gmail.com", Cypress.env("demoPassword"));
|
||||
cy.apiLogin("user@gmail.com", Cypress.env("demoPassword"), "portal");
|
||||
cy.apiLogin("user2@gmail.com", Cypress.env("demoPassword"), "portal");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("flow: customer and staff see the same world", () => {
|
||||
.its("status")
|
||||
.should("eq", 200);
|
||||
});
|
||||
cy.apiLogin("user@gmail.com", Cypress.env("demoPassword")).then(({ token }) => {
|
||||
cy.apiLogin("user@gmail.com", Cypress.env("demoPassword"), "portal").then(({ token }) => {
|
||||
cy.request({
|
||||
url: `${api()}/api/me`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
|
||||
@@ -45,9 +45,11 @@ export function db<T = Row>(sql: string, params: unknown[] = []) {
|
||||
|
||||
/** Bearer token for a staff/customer account (portal users use the demo pwd). */
|
||||
export function tokenFor(email: string): Cypress.Chainable<string> {
|
||||
const pass =
|
||||
email.endsWith("@gmail.com") ? (Cypress.env("demoPassword") as string) : undefined;
|
||||
return cy.apiLogin(email, pass).then(({ token }) => cy.wrap(token, { log: false }));
|
||||
const isPortalUser = email.endsWith("@gmail.com");
|
||||
const pass = isPortalUser ? (Cypress.env("demoPassword") as string) : undefined;
|
||||
return cy
|
||||
.apiLogin(email, pass, isPortalUser ? "portal" : "backoffice")
|
||||
.then(({ token }) => cy.wrap(token, { log: false }));
|
||||
}
|
||||
|
||||
export function apiPost(
|
||||
|
||||
@@ -55,26 +55,29 @@ where not exists (
|
||||
);
|
||||
|
||||
-- ── Users ────────────────────────────────────────────────────────────────────
|
||||
-- user_type matters now: EDRFREIGHT-415's /auth/login audience gate rejects an
|
||||
-- employee-typed account on the portal client and vice versa, so the two demo
|
||||
-- "customer" accounts must actually be typed 'individual', not 'employee'.
|
||||
insert into iam.users (id, email, username, name, status, is_active, has_set_password, user_type)
|
||||
select gen_random_uuid(), v.email, v.username, jsonb_build_object('en', v.display),
|
||||
'accepted', true, true, 'employee'
|
||||
'accepted', true, true, v.user_type
|
||||
from (values
|
||||
('linestaff@edr.local', 'linestaff', 'linestaff'),
|
||||
('chief@edr.local', 'chief', 'chief'),
|
||||
('director@edr.local', 'director', 'director'),
|
||||
('ceo@edr.local', 'ceo', 'ceo'),
|
||||
('marketer@edr.local', 'marketer', 'marketer'),
|
||||
('operation@edr.local', 'operation', 'operation'),
|
||||
('gl-et@edr.local', 'gl_et', 'gl_et'),
|
||||
('gl-dj@edr.local', 'gl_dj', 'gl_dj'),
|
||||
('user@gmail.com', 'user', 'Demo User 1'),
|
||||
('user2@gmail.com', 'user2', 'Demo User 2'),
|
||||
('linestaff@edr.local', 'linestaff', 'linestaff', 'employee'),
|
||||
('chief@edr.local', 'chief', 'chief', 'employee'),
|
||||
('director@edr.local', 'director', 'director', 'employee'),
|
||||
('ceo@edr.local', 'ceo', 'ceo', 'employee'),
|
||||
('marketer@edr.local', 'marketer', 'marketer', 'employee'),
|
||||
('operation@edr.local', 'operation', 'operation', 'employee'),
|
||||
('gl-et@edr.local', 'gl_et', 'gl_et', 'employee'),
|
||||
('gl-dj@edr.local', 'gl_dj', 'gl_dj', 'employee'),
|
||||
('user@gmail.com', 'user', 'Demo User 1', 'individual'),
|
||||
('user2@gmail.com', 'user2', 'Demo User 2', 'individual'),
|
||||
-- Full-authority operator the corridor specs drive the customs/GL steps with
|
||||
-- (t1-close, risk, gate pass, second duty, import release). Nothing in the
|
||||
-- repo seeded it before — the suite silently relied on a hand-made row that
|
||||
-- only existed in a long-lived dev database, so a fresh stack failed 7 specs.
|
||||
('superadmin@tria.com', 'superadmin', 'Super Admin')
|
||||
) v(email, username, display)
|
||||
('superadmin@tria.com', 'superadmin', 'Super Admin', 'employee')
|
||||
) v(email, username, display, user_type)
|
||||
where not exists (select 1 from iam.users u where u.email = v.email);
|
||||
|
||||
-- ── Credentials ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -21,11 +21,17 @@ export interface LoginBody {
|
||||
const apiUrl = () => Cypress.env("apiUrl") as string;
|
||||
const password = () => Cypress.env("defaultPassword") as string;
|
||||
|
||||
function apiLogin(email: string, pass?: string): Cypress.Chainable<LoginBody> {
|
||||
function apiLogin(
|
||||
email: string,
|
||||
pass?: string,
|
||||
app: "backoffice" | "portal" = "backoffice",
|
||||
): Cypress.Chainable<LoginBody> {
|
||||
return cy
|
||||
.request<LoginBody>("POST", `${apiUrl()}/api/auth/login`, {
|
||||
email,
|
||||
password: pass ?? password(),
|
||||
.request<LoginBody>({
|
||||
method: "POST",
|
||||
url: `${apiUrl()}/api/auth/login`,
|
||||
body: { email, password: pass ?? password() },
|
||||
headers: { "x-client-app": app },
|
||||
})
|
||||
.then((response) => {
|
||||
expect(response.status).to.eq(201);
|
||||
@@ -38,7 +44,7 @@ function sessionFor(app: "backoffice" | "portal", email: string, pass?: string)
|
||||
cy.session(
|
||||
[app, email],
|
||||
() => {
|
||||
apiLogin(email, pass).then(({ token, refreshToken }) => {
|
||||
apiLogin(email, pass, app).then(({ token, refreshToken }) => {
|
||||
cy.setCookie("auth-token", token);
|
||||
cy.setCookie("refresh-token", refreshToken);
|
||||
});
|
||||
@@ -60,7 +66,11 @@ function sessionFor(app: "backoffice" | "portal", email: string, pass?: string)
|
||||
);
|
||||
}
|
||||
|
||||
Cypress.Commands.add("apiLogin", (email: string, pass?: string) => apiLogin(email, pass));
|
||||
Cypress.Commands.add(
|
||||
"apiLogin",
|
||||
(email: string, pass?: string, app: "backoffice" | "portal" = "backoffice") =>
|
||||
apiLogin(email, pass, app),
|
||||
);
|
||||
|
||||
Cypress.Commands.add("loginBackoffice", (email = "ceo@edr.local", pass?: string) => {
|
||||
sessionFor("backoffice", email, pass);
|
||||
@@ -260,7 +270,11 @@ declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/** POST /api/auth/login, returns the flattened token body. */
|
||||
apiLogin(email: string, pass?: string): Chainable<LoginBody>;
|
||||
apiLogin(
|
||||
email: string,
|
||||
pass?: string,
|
||||
app?: "backoffice" | "portal",
|
||||
): Chainable<LoginBody>;
|
||||
/** Cached programmatic staff session (default ceo@edr.local). */
|
||||
loginBackoffice(email?: string, pass?: string): Chainable<void>;
|
||||
/** Cached programmatic customer session (default user@gmail.com). */
|
||||
|
||||
Reference in New Issue
Block a user