c821b2ca07
CI / verify (push) Successful in 2m55s
The seam existed with only a Supabase implementation, so an on-prem deployment had no way to authenticate. A customer running PIG inside their own network already has Okta, Entra, Keycloak, Auth0 or Google Workspace; asking them to stand up a second identity system is a serious adoption tax and in a regulated environment usually refused outright. Setting PIG_OIDC_ISSUER is normally the whole configuration — the JWKS is discovered from the issuer's well-known document. PIG_OIDC_JWKS_URI skips discovery entirely for an air-gapped network. OIDC takes precedence over Supabase so an on-prem install can leave the hosted values in its environment file without them quietly taking over. Three decisions worth stating: Discovery is resolved lazily and the FAILURE is not cached. Doing it per request would put the customer's identity provider on the critical path of every API call; doing it eagerly at boot would mean their IdP rebooting takes the CRM down with it. So it happens on first use and retries on the next request. The audience check is optional but warned about loudly. Without it, a token the provider issued for ANY other application in the same tenant verifies here — a token minted for an unrelated internal tool would be accepted as a PIG session. It cannot be mandatory because some providers legitimately issue single-audience tokens. Email falls back through email, preferred_username and upn, because providers disagree, but a preferred_username without an "@" is ignored — PIG keys membership on the address, and a bare username must never become an account identity. Also fixed a warning that claimed "authentication is DISABLED" on a correctly configured OIDC deployment. That is worse than silence: an operator who reads it on a secure install learns to ignore the warnings. The dev bypass itself was already correct — it keys on the resolved provider rather than on Supabase. 18 new tests, most of them about what the provider must REFUSE: a foreign signing key, a foreign issuer, a token for a different application, an expired token, a token with no subject, and a discovery outage that must not become permanent. Keys are generated per test and the JWKS is served locally, so they run offline. Verified: production refuses to start with neither provider, starts with OIDC alone, enforces 401 on an unauthenticated request, and warns only about the genuinely missing admin list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
357 lines
12 KiB
TypeScript
357 lines
12 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { createHash, randomUUID } from 'node:crypto';
|
|
import test, { after } from 'node:test';
|
|
import { and, eq, inArray } from 'drizzle-orm';
|
|
import {
|
|
accounts,
|
|
activities,
|
|
allocations,
|
|
capacityCommitments,
|
|
createDatabase,
|
|
demandDeals,
|
|
invites,
|
|
teamMemberships,
|
|
users,
|
|
} from '@pig/db';
|
|
import { createApp } from '../src/app';
|
|
import { loadConfig } from '../src/lib/config';
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) throw new Error('DATABASE_URL is required for the critical-path E2E test.');
|
|
|
|
const db = createDatabase({ url: databaseUrl, max: 4 });
|
|
|
|
after(async () => {
|
|
await db.$client.end();
|
|
});
|
|
|
|
test('invite-bound member creates, sells and observes capacity through authenticated HTTP', async () => {
|
|
const suffix = randomUUID();
|
|
const email = `e2e-${suffix}@example.test`;
|
|
const subject = randomUUID();
|
|
const accessToken = `e2e-token-${suffix}`;
|
|
const inviteCode = `invite-${suffix}`;
|
|
const startsAt = new Date('2027-01-01T00:00:00.000Z');
|
|
const endsAt = new Date('2027-01-01T10:00:00.000Z');
|
|
let inviteId: string | undefined;
|
|
let userId: string | undefined;
|
|
let supplyAccountId: string | undefined;
|
|
let demandAccountId: string | undefined;
|
|
let demandDealId: string | undefined;
|
|
let commitmentId: string | undefined;
|
|
let allocationId: string | undefined;
|
|
|
|
const config = loadConfig({
|
|
...process.env,
|
|
NODE_ENV: 'production',
|
|
DATABASE_URL: databaseUrl,
|
|
PIG_PUBLIC_URL: 'https://pig-e2e.invalid',
|
|
SUPABASE_URL: 'https://identity-e2e.invalid',
|
|
SUPABASE_ANON_KEY: 'e2e-anon-key',
|
|
SUPABASE_SERVICE_KEY: '',
|
|
PIG_ADMIN_EMAILS: '',
|
|
PIGGY_ENABLED: 'false',
|
|
});
|
|
const authProvider = {
|
|
name: 'e2e-stub',
|
|
async verifyAccessToken(token: string) {
|
|
if (token !== accessToken) throw new Error('Invalid E2E token.');
|
|
return { subject, email };
|
|
},
|
|
};
|
|
const app = createApp(config, db, authProvider);
|
|
|
|
try {
|
|
const [invite] = await db
|
|
.insert(invites)
|
|
.values({
|
|
codeHash: createHash('sha256').update(inviteCode).digest('hex'),
|
|
email,
|
|
usesRemaining: 1,
|
|
})
|
|
.returning();
|
|
assert.ok(invite);
|
|
inviteId = invite.id;
|
|
|
|
// A valid external identity is authentication, not workspace membership.
|
|
const ungatedSignup = await request(app, '/api/signup', {
|
|
token: accessToken,
|
|
body: { name: 'E2E Capacity Seller', team: 'supply' },
|
|
});
|
|
assert.equal(ungatedSignup.status, 403);
|
|
assert.equal(ungatedSignup.body.code, 'invite_required');
|
|
const uninvitedUsers = await db.select().from(users).where(eq(users.email, email));
|
|
assert.equal(uninvitedUsers.length, 0);
|
|
|
|
// The password registration route also stays closed without its provider
|
|
// administration credential; the injected verifier is not a bypass.
|
|
const openRegistration = await request(app, '/api/register', {
|
|
body: {
|
|
email,
|
|
password: 'not-a-production-password',
|
|
name: 'E2E Capacity Seller',
|
|
team: 'supply',
|
|
inviteCode,
|
|
},
|
|
});
|
|
assert.equal(openRegistration.status, 503);
|
|
assert.equal(openRegistration.body.code, 'registration_unavailable');
|
|
|
|
const signup = await request(app, '/api/signup', {
|
|
token: accessToken,
|
|
body: {
|
|
name: 'E2E Capacity Seller',
|
|
team: 'supply',
|
|
title: 'Capacity lead',
|
|
inviteCode,
|
|
},
|
|
});
|
|
assert.equal(signup.status, 201);
|
|
const signupUser = record(signup.body.user, 'signup user');
|
|
userId = text(signupUser.id, 'signup user id');
|
|
assert.equal(signupUser.email, email);
|
|
assert.equal(signupUser.isPlatformAdmin, false);
|
|
|
|
const [consumedInvite] = await db
|
|
.select()
|
|
.from(invites)
|
|
.where(eq(invites.id, inviteId));
|
|
assert.ok(consumedInvite);
|
|
assert.equal(consumedInvite.usesRemaining, 0);
|
|
assert.equal(consumedInvite.redeemedByUserId, userId);
|
|
|
|
// Fixture setup grants only the two capabilities this path needs. Product
|
|
// writes below still pass through the real authorization middleware.
|
|
await db
|
|
.update(teamMemberships)
|
|
.set({ role: 'lead' })
|
|
.where(
|
|
and(
|
|
eq(teamMemberships.userId, userId),
|
|
eq(teamMemberships.team, 'supply'),
|
|
),
|
|
);
|
|
await db.insert(teamMemberships).values({
|
|
userId,
|
|
team: 'demand',
|
|
role: 'member',
|
|
isPrimary: false,
|
|
});
|
|
|
|
const me = await request(app, '/api/me', { token: accessToken, method: 'GET' });
|
|
assert.equal(me.status, 200);
|
|
assert.equal(me.body.id, userId);
|
|
assert.equal(me.body.via, 'jwt');
|
|
const grants = array(me.body.permissions, 'permissions');
|
|
assert.ok(
|
|
grants.some(
|
|
(grant) =>
|
|
record(grant, 'permission').capability === 'commitment:write' &&
|
|
record(grant, 'permission').team === 'supply',
|
|
),
|
|
);
|
|
assert.ok(
|
|
grants.some(
|
|
(grant) =>
|
|
record(grant, 'permission').capability === 'deal:write' &&
|
|
record(grant, 'permission').team === 'demand',
|
|
),
|
|
);
|
|
|
|
const [supplyAccount, demandAccount] = await Promise.all([
|
|
db
|
|
.insert(accounts)
|
|
.values({ name: `E2E Supply ${suffix}`, side: 'supply' })
|
|
.returning()
|
|
.then(([row]) => row),
|
|
db
|
|
.insert(accounts)
|
|
.values({ name: `E2E Demand ${suffix}`, side: 'demand' })
|
|
.returning()
|
|
.then(([row]) => row),
|
|
]);
|
|
assert.ok(supplyAccount);
|
|
assert.ok(demandAccount);
|
|
supplyAccountId = supplyAccount.id;
|
|
demandAccountId = demandAccount.id;
|
|
|
|
const [deal] = await db
|
|
.insert(demandDeals)
|
|
.values({
|
|
accountId: demandAccount.id,
|
|
name: `E2E Reserved Cluster ${suffix}`,
|
|
stage: 'deployment',
|
|
productLine: 'compute_reserved',
|
|
ownerUserId: userId,
|
|
})
|
|
.returning();
|
|
assert.ok(deal);
|
|
demandDealId = deal.id;
|
|
|
|
const baselineMarginResponse = await request(app, '/api/capacity/margin', {
|
|
token: accessToken,
|
|
method: 'GET',
|
|
});
|
|
assert.equal(baselineMarginResponse.status, 200);
|
|
const baselineMargin = marginTotals(baselineMarginResponse.body);
|
|
|
|
const commitmentResponse = await request(app, '/api/commitments', {
|
|
token: accessToken,
|
|
body: {
|
|
accountId: supplyAccount.id,
|
|
name: `E2E Eight-GPU Block ${suffix}`,
|
|
gpuType: 'H100_80GB',
|
|
gpuCount: 8,
|
|
interconnectType: 'Unknown',
|
|
securityTier: 'secure_cloud',
|
|
startsAt: startsAt.toISOString(),
|
|
endsAt: endsAt.toISOString(),
|
|
totalGpuHours: 80,
|
|
costPerGpuHourCents: 100,
|
|
},
|
|
});
|
|
assert.equal(commitmentResponse.status, 200);
|
|
commitmentId = text(commitmentResponse.body.id, 'commitment id');
|
|
|
|
const availabilityBefore = await request(app, '/api/capacity/availability', {
|
|
token: accessToken,
|
|
method: 'GET',
|
|
});
|
|
assert.equal(availabilityBefore.status, 200);
|
|
const capacityBefore = findById(
|
|
array(availabilityBefore.body, 'availability'),
|
|
'commitmentId',
|
|
commitmentId,
|
|
);
|
|
assert.equal(capacityBefore.totalGpuHours, 80);
|
|
assert.equal(capacityBefore.soldGpuHours, 0);
|
|
assert.equal(capacityBefore.availableGpuHours, 80);
|
|
|
|
const marginBeforeResponse = await request(app, '/api/capacity/margin', {
|
|
token: accessToken,
|
|
method: 'GET',
|
|
});
|
|
assert.equal(marginBeforeResponse.status, 200);
|
|
const marginBefore = marginTotals(marginBeforeResponse.body);
|
|
assert.equal(marginBefore.revenueCents - baselineMargin.revenueCents, 0);
|
|
assert.equal(marginBefore.costCents - baselineMargin.costCents, 8_000);
|
|
assert.equal(marginBefore.grossMarginCents - baselineMargin.grossMarginCents, -8_000);
|
|
|
|
const allocationResponse = await request(app, '/api/allocations', {
|
|
token: accessToken,
|
|
body: {
|
|
capacityCommitmentId: commitmentId,
|
|
demandDealId: deal.id,
|
|
gpuHours: 40,
|
|
pricePerGpuHourCents: 300,
|
|
startsAt: startsAt.toISOString(),
|
|
endsAt: endsAt.toISOString(),
|
|
status: 'committed',
|
|
},
|
|
});
|
|
assert.equal(allocationResponse.status, 200);
|
|
allocationId = text(allocationResponse.body.id, 'allocation id');
|
|
|
|
const availabilityAfter = await request(app, '/api/capacity/availability', {
|
|
token: accessToken,
|
|
method: 'GET',
|
|
});
|
|
const capacityAfter = findById(
|
|
array(availabilityAfter.body, 'availability'),
|
|
'commitmentId',
|
|
commitmentId,
|
|
);
|
|
assert.equal(capacityAfter.soldGpuHours, 40);
|
|
assert.equal(capacityAfter.heldGpuHours, 0);
|
|
assert.equal(capacityAfter.availableGpuHours, 40);
|
|
assert.equal(capacityAfter.utilisation, 0.5);
|
|
|
|
const marginAfterResponse = await request(app, '/api/capacity/margin', {
|
|
token: accessToken,
|
|
method: 'GET',
|
|
});
|
|
assert.equal(marginAfterResponse.status, 200);
|
|
const marginAfter = marginTotals(marginAfterResponse.body);
|
|
assert.equal(marginAfter.revenueCents - marginBefore.revenueCents, 12_000);
|
|
// The full block remains the cost basis even though only half was sold.
|
|
assert.equal(marginAfter.costCents - marginBefore.costCents, 0);
|
|
assert.equal(marginAfter.grossMarginCents - marginBefore.grossMarginCents, 12_000);
|
|
assert.equal(marginAfter.grossMarginCents - baselineMargin.grossMarginCents, 4_000);
|
|
} finally {
|
|
if (allocationId) await db.delete(allocations).where(eq(allocations.id, allocationId));
|
|
if (commitmentId) {
|
|
await db.delete(capacityCommitments).where(eq(capacityCommitments.id, commitmentId));
|
|
}
|
|
if (demandDealId) await db.delete(demandDeals).where(eq(demandDeals.id, demandDealId));
|
|
if (userId) await db.delete(activities).where(eq(activities.actorUserId, userId));
|
|
const accountIds = [supplyAccountId, demandAccountId].filter(
|
|
(id): id is string => id !== undefined,
|
|
);
|
|
if (accountIds.length) await db.delete(accounts).where(inArray(accounts.id, accountIds));
|
|
if (inviteId) await db.delete(invites).where(eq(invites.id, inviteId));
|
|
if (userId) await db.delete(users).where(eq(users.id, userId));
|
|
}
|
|
});
|
|
|
|
async function request(
|
|
app: ReturnType<typeof createApp>,
|
|
path: string,
|
|
options: { token?: string; body?: unknown; method?: 'GET' | 'POST' } = {},
|
|
): Promise<{ status: number; body: Record<string, unknown> }> {
|
|
const response = await app.request(path, {
|
|
method: options.method ?? 'POST',
|
|
headers: {
|
|
...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
|
|
...(options.body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
},
|
|
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
});
|
|
return {
|
|
status: response.status,
|
|
body: (await response.json()) as Record<string, unknown>,
|
|
};
|
|
}
|
|
|
|
function record(value: unknown, label: string): Record<string, unknown> {
|
|
assert.ok(value && typeof value === 'object' && !Array.isArray(value), `${label} must be an object`);
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function array(value: unknown, label: string): unknown[] {
|
|
assert.ok(Array.isArray(value), `${label} must be an array`);
|
|
return value;
|
|
}
|
|
|
|
function text(value: unknown, label: string): string {
|
|
if (typeof value !== 'string') {
|
|
assert.fail(`${label} must be a string`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function findById(rows: unknown[], field: string, id: string): Record<string, unknown> {
|
|
const found = rows.map((row) => record(row, 'row')).find((row) => row[field] === id);
|
|
assert.ok(found, `Expected ${field}=${id}`);
|
|
return found;
|
|
}
|
|
|
|
function marginTotals(body: Record<string, unknown>): {
|
|
revenueCents: number;
|
|
costCents: number;
|
|
grossMarginCents: number;
|
|
} {
|
|
const totals = record(body.totals, 'margin totals');
|
|
return {
|
|
revenueCents: number(totals.revenueCents, 'margin revenue'),
|
|
costCents: number(totals.costCents, 'margin cost'),
|
|
grossMarginCents: number(totals.grossMarginCents, 'gross margin'),
|
|
};
|
|
}
|
|
|
|
function number(value: unknown, label: string): number {
|
|
if (typeof value !== 'number') {
|
|
assert.fail(`${label} must be a number`);
|
|
}
|
|
return value;
|
|
}
|