Combines testing improvements with org invites feature: - Sessions and devices now use subrouter structure (me.sessions.*, me.devices.*) - Added me.invites subrouter for org invitations - Updated test scripts to include coverage and unit tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
298 lines
8.6 KiB
TypeScript
298 lines
8.6 KiB
TypeScript
import type {
|
|
AuthenticationResponseJSON,
|
|
PublicKeyCredentialCreationOptionsJSON,
|
|
PublicKeyCredentialRequestOptionsJSON,
|
|
RegistrationResponseJSON,
|
|
} from "@simplewebauthn/types";
|
|
import { oc } from "@orpc/contract";
|
|
import * as z from "zod";
|
|
import {
|
|
adminAddSiteInputSchema,
|
|
adminCreateOrgInputSchema,
|
|
adminCreateUserInputSchema,
|
|
adminUpdateUserInputSchema,
|
|
} from "./schemas/admin.js";
|
|
import {
|
|
forgotPasswordInputSchema,
|
|
loginPasswordInputSchema,
|
|
loginRequestInputSchema,
|
|
loginRequestOutputSchema,
|
|
loginStatusOutputSchema,
|
|
resetPasswordInputSchema,
|
|
signupInputSchema,
|
|
verifyEmailInputSchema,
|
|
} from "./schemas/auth.js";
|
|
import {
|
|
emailSchema,
|
|
slugSchema,
|
|
successResponseSchema,
|
|
} from "./schemas/common.js";
|
|
import {
|
|
createInviteInputSchema,
|
|
createOrgInputSchema,
|
|
orgInviteOutputSchema,
|
|
orgMemberOutputSchema,
|
|
orgOutputSchema,
|
|
orgSiteOutputSchema,
|
|
updateMemberRoleInputSchema,
|
|
} from "./schemas/org.js";
|
|
import {
|
|
authStatusOutputSchema,
|
|
deviceOutputSchema,
|
|
passkeyOutputSchema,
|
|
sessionOutputSchema,
|
|
setPasswordInputSchema,
|
|
setupProfileInputSchema,
|
|
trustDeviceInputSchema,
|
|
updateProfileInputSchema,
|
|
userInviteOutputSchema,
|
|
userProfileSchema,
|
|
} from "./schemas/user.js";
|
|
|
|
/**
|
|
* oRPC API Contract for the Publisher Dashboard
|
|
* This defines all RPC procedure signatures served at /api/v1/rpc
|
|
*/
|
|
export const contract = oc.router({
|
|
auth: oc.router({
|
|
// Signup and verification
|
|
signup: oc.input(signupInputSchema).output(successResponseSchema),
|
|
verifyEmail: oc.input(verifyEmailInputSchema).output(successResponseSchema),
|
|
resendVerificationEmail: oc.output(successResponseSchema),
|
|
|
|
// Login flow
|
|
createLoginRequest: oc
|
|
.input(loginRequestInputSchema)
|
|
.output(loginRequestOutputSchema),
|
|
loginPassword: oc
|
|
.input(loginPasswordInputSchema)
|
|
.output(successResponseSchema),
|
|
loginPasswordConfirm: oc
|
|
.input(z.object({ token: z.string() }))
|
|
.output(successResponseSchema),
|
|
loginIfRequestIsCompleted: oc.output(loginStatusOutputSchema),
|
|
|
|
// Password reset
|
|
forgotPassword: oc
|
|
.input(forgotPasswordInputSchema)
|
|
.output(successResponseSchema),
|
|
resetPassword: oc
|
|
.input(resetPasswordInputSchema)
|
|
.output(successResponseSchema),
|
|
|
|
// Logout
|
|
logout: oc.output(successResponseSchema),
|
|
|
|
// WebAuthn procedures
|
|
webauthn: oc.router({
|
|
createRegistrationOptions: oc
|
|
.input(z.object({ email: emailSchema }))
|
|
.output(
|
|
z.object({
|
|
challengeId: z.number(),
|
|
options: z.custom<PublicKeyCredentialCreationOptionsJSON>(),
|
|
}),
|
|
),
|
|
verifyRegistration: oc
|
|
.input(
|
|
z.object({
|
|
challengeId: z.number(),
|
|
response: z.custom<RegistrationResponseJSON>(),
|
|
}),
|
|
)
|
|
.output(z.object({ passkeyId: z.number() })),
|
|
createAuthenticationOptions: oc.output(
|
|
z.object({
|
|
challengeId: z.number(),
|
|
options: z.custom<PublicKeyCredentialRequestOptionsJSON>(),
|
|
}),
|
|
),
|
|
verifyAuthentication: oc
|
|
.input(
|
|
z.object({
|
|
challengeId: z.number(),
|
|
response: z.custom<AuthenticationResponseJSON>(),
|
|
}),
|
|
)
|
|
.output(successResponseSchema),
|
|
}),
|
|
}),
|
|
|
|
me: oc.router({
|
|
// Profile
|
|
get: oc.output(userProfileSchema),
|
|
setupProfile: oc
|
|
.input(setupProfileInputSchema)
|
|
.output(successResponseSchema),
|
|
updateProfile: oc
|
|
.input(updateProfileInputSchema)
|
|
.output(successResponseSchema),
|
|
delete: oc
|
|
.input(z.object({ password: z.string() }))
|
|
.output(successResponseSchema),
|
|
|
|
// Auth status (for CLI and debugging)
|
|
authStatus: oc.output(authStatusOutputSchema),
|
|
|
|
// Authentication settings
|
|
setPassword: oc.input(setPasswordInputSchema).output(successResponseSchema),
|
|
|
|
// Passkeys
|
|
passkeys: oc.router({
|
|
list: oc.output(z.array(passkeyOutputSchema)),
|
|
rename: oc
|
|
.input(z.object({ passkeyId: z.number(), name: z.string() }))
|
|
.output(successResponseSchema),
|
|
delete: oc
|
|
.input(z.object({ passkeyId: z.number() }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Org invites for the current user
|
|
invites: oc.router({
|
|
list: oc.output(z.array(userInviteOutputSchema)),
|
|
get: oc
|
|
.input(z.object({ inviteId: z.number() }))
|
|
.output(userInviteOutputSchema),
|
|
accept: oc
|
|
.input(z.object({ inviteId: z.number() }))
|
|
.output(successResponseSchema),
|
|
decline: oc
|
|
.input(z.object({ inviteId: z.number() }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Sessions
|
|
sessions: oc.router({
|
|
list: oc.output(z.array(sessionOutputSchema)),
|
|
revoke: oc
|
|
.input(z.object({ sessionId: z.number() }))
|
|
.output(successResponseSchema),
|
|
revokeAll: oc.output(successResponseSchema),
|
|
}),
|
|
|
|
// Devices
|
|
devices: oc.router({
|
|
getInfo: oc.output(deviceOutputSchema),
|
|
trust: oc.input(trustDeviceInputSchema).output(successResponseSchema),
|
|
listTrusted: oc.output(z.array(deviceOutputSchema)),
|
|
untrust: oc
|
|
.input(z.object({ deviceId: z.number() }))
|
|
.output(successResponseSchema),
|
|
revokeAll: oc.output(successResponseSchema),
|
|
}),
|
|
}),
|
|
|
|
orgs: oc.router({
|
|
// Org management
|
|
list: oc.output(z.array(orgOutputSchema)),
|
|
create: oc
|
|
.input(createOrgInputSchema)
|
|
.output(z.object({ slug: z.string() })),
|
|
get: oc.input(z.object({ slug: slugSchema })).output(orgOutputSchema),
|
|
update: oc
|
|
.input(
|
|
z.object({
|
|
slug: slugSchema,
|
|
displayName: z.string().optional(),
|
|
logoUrl: z.string().optional(),
|
|
}),
|
|
)
|
|
.output(successResponseSchema),
|
|
delete: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(successResponseSchema),
|
|
leave: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(successResponseSchema),
|
|
|
|
// Members
|
|
members: oc.router({
|
|
list: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(z.array(orgMemberOutputSchema)),
|
|
updateRole: oc
|
|
.input(updateMemberRoleInputSchema)
|
|
.output(successResponseSchema),
|
|
remove: oc
|
|
.input(z.object({ slug: slugSchema, userId: z.number() }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Invites
|
|
invites: oc.router({
|
|
list: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(z.array(orgInviteOutputSchema)),
|
|
create: oc.input(createInviteInputSchema).output(successResponseSchema),
|
|
cancel: oc
|
|
.input(z.object({ slug: slugSchema, inviteId: z.number() }))
|
|
.output(successResponseSchema),
|
|
accept: oc
|
|
.input(z.object({ token: z.string() }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Sites
|
|
sites: oc.router({
|
|
list: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(z.array(orgSiteOutputSchema)),
|
|
}),
|
|
}),
|
|
|
|
admin: oc.router({
|
|
// Admin org management
|
|
orgs: oc.router({
|
|
list: oc.output(z.array(orgOutputSchema)),
|
|
get: oc.input(z.object({ slug: slugSchema })).output(orgOutputSchema),
|
|
create: oc
|
|
.input(adminCreateOrgInputSchema)
|
|
.output(z.object({ slug: z.string() })),
|
|
update: oc
|
|
.input(
|
|
z.object({
|
|
slug: slugSchema,
|
|
displayName: z.string().optional(),
|
|
logoUrl: z.string().optional(),
|
|
}),
|
|
)
|
|
.output(successResponseSchema),
|
|
delete: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(successResponseSchema),
|
|
listSites: oc
|
|
.input(z.object({ slug: slugSchema }))
|
|
.output(z.array(orgSiteOutputSchema)),
|
|
addSite: oc.input(adminAddSiteInputSchema).output(successResponseSchema),
|
|
removeSite: oc
|
|
.input(z.object({ slug: slugSchema, domain: z.string() }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Admin user management
|
|
users: oc.router({
|
|
list: oc.output(z.array(userProfileSchema)),
|
|
get: oc.input(z.object({ email: emailSchema })).output(userProfileSchema),
|
|
create: oc
|
|
.input(adminCreateUserInputSchema)
|
|
.output(successResponseSchema),
|
|
update: oc
|
|
.input(adminUpdateUserInputSchema)
|
|
.output(successResponseSchema),
|
|
confirmEmail: oc
|
|
.input(z.object({ email: emailSchema }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
|
|
// Admin auth management
|
|
auth: oc.router({
|
|
completeLogin: oc
|
|
.input(z.object({ email: emailSchema }))
|
|
.output(successResponseSchema),
|
|
}),
|
|
}),
|
|
});
|
|
|
|
export type APIContract = typeof contract;
|