Bind each customer to a Verae userId for hop tracing
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Signup registers/binds a Verae central user and stores veraeUserId. Public access stays the zappier API key. Chain JWTs stay server-side behind tokenRef. Authz, billing, and jobs.watch carry veraeUserId.
This commit is contained in:
parent
1b199ca4d4
commit
345aeeead9
79 changed files with 703 additions and 95 deletions
76
packages/zappier/src/verae-bind.ts
Normal file
76
packages/zappier/src/verae-bind.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Bind a portal customer to a Verae central user id.
|
||||
* The public credential stays the zappier API key. The Verae JWT never leaves the server.
|
||||
*/
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
|
||||
export function normalizeVeraeUsername(username: string): string {
|
||||
return username.trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function stableVeraeUserId(username: string): string {
|
||||
const n = normalizeVeraeUsername(username);
|
||||
return `vu_${createHash('sha256').update(n).digest('hex').slice(0, 16)}`;
|
||||
}
|
||||
|
||||
export type VeraeBind = {
|
||||
veraeUserId: string;
|
||||
veraeUsername: string;
|
||||
bound: boolean;
|
||||
};
|
||||
|
||||
function mockBind(email: string): VeraeBind {
|
||||
const veraeUsername = normalizeVeraeUsername(email);
|
||||
return { veraeUserId: stableVeraeUserId(veraeUsername), veraeUsername, bound: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or look up the customer on api.veraetime.net.
|
||||
* MOCK_VERAE (default) or missing VERAE_API_BASE_URL → stable id, no network.
|
||||
*/
|
||||
export async function bindVeraeUser(email: string): Promise<VeraeBind> {
|
||||
const mock = process.env.MOCK_VERAE !== 'false';
|
||||
const base = (process.env.VERAE_API_BASE_URL || '').replace(/\/$/, '');
|
||||
if (mock || !base) return mockBind(email);
|
||||
|
||||
const veraeUsername = normalizeVeraeUsername(email);
|
||||
const adminUser = process.env.VERAE_ADMIN_USER;
|
||||
const adminPass = process.env.VERAE_ADMIN_PASSWORD;
|
||||
const password = `vt_${randomBytes(18).toString('base64url')}`;
|
||||
|
||||
const login = async (username: string, pass: string) => {
|
||||
const r = await fetch(`${base}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ username, password: pass }),
|
||||
});
|
||||
if (!r.ok) throw new Error(`verae login ${r.status}`);
|
||||
return r.json() as Promise<{ token: string; user?: { id?: string; username?: string } }>;
|
||||
};
|
||||
|
||||
if (!adminUser || !adminPass) return mockBind(email);
|
||||
|
||||
try {
|
||||
const admin = await login(adminUser, adminPass);
|
||||
const created = await fetch(`${base}/auth/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: `Bearer ${admin.token}`,
|
||||
},
|
||||
body: JSON.stringify({ username: veraeUsername, password, role: 'user' }),
|
||||
});
|
||||
if (!created.ok && created.status !== 409) {
|
||||
return mockBind(email);
|
||||
}
|
||||
const user = (await created.json().catch(() => ({}))) as { id?: string; username?: string };
|
||||
const id = user.id || (await login(veraeUsername, password)).user?.id;
|
||||
return {
|
||||
veraeUserId: id || stableVeraeUserId(veraeUsername),
|
||||
veraeUsername,
|
||||
bound: true,
|
||||
};
|
||||
} catch {
|
||||
return mockBind(email);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue