feat(POSTV1-03): 增加本机测试直达入口
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
This commit is contained in:
@@ -263,6 +263,7 @@ export interface CreateAppOptions {
|
||||
eventHub?: EventHub;
|
||||
generations?: GenerationSubmissionService;
|
||||
latestExports?: LatestExportService;
|
||||
localTestAuth?: boolean;
|
||||
models?: ModelConfigurationService;
|
||||
networkBoundary?: NetworkBoundaryOptions;
|
||||
publicAssets?: PublicAssetResolver;
|
||||
@@ -2075,6 +2076,45 @@ export async function createApp(options: CreateAppOptions = {}) {
|
||||
},
|
||||
);
|
||||
|
||||
if (options.localTestAuth && options.registration) {
|
||||
app.get(
|
||||
"/api/v1/auth/local-test",
|
||||
{ schema: { hide: true } },
|
||||
async () => ({ available: true }),
|
||||
);
|
||||
app.post(
|
||||
"/api/v1/auth/local-test",
|
||||
{ schema: { hide: true } },
|
||||
async (request, reply) => {
|
||||
try {
|
||||
const result = options.registration!.createLocalTestSession();
|
||||
reply.header(
|
||||
"Set-Cookie",
|
||||
`${userSessionCookieName}=${result.sessionToken}; Max-Age=${30 * 24 * 60 * 60}; Path=/; HttpOnly; SameSite=Strict`,
|
||||
);
|
||||
return {
|
||||
audience: result.audience,
|
||||
credits: {
|
||||
available_balance: result.credits.availableBalance,
|
||||
reserved_balance: result.credits.reservedBalance,
|
||||
},
|
||||
session_expires_at: new Date(result.sessionExpiresAt).toISOString(),
|
||||
status: result.status,
|
||||
user: {
|
||||
creator_name: result.user.creatorName,
|
||||
role: result.user.role,
|
||||
social_id: result.user.socialId,
|
||||
status: result.user.status,
|
||||
user_id: result.user.userId,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return registrationFailure(reply, request.id, error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
app.post(
|
||||
"/api/v1/auth/login/complete",
|
||||
{
|
||||
|
||||
@@ -31,11 +31,13 @@ let models: ModelConfigurationService | undefined;
|
||||
let recentAssets: RecentAssetService | undefined;
|
||||
let stickers: StickerReleaseService | undefined;
|
||||
let amap: AmapAdapter = new MockAmapAdapter();
|
||||
let localTestAuth = false;
|
||||
const instanceConfigPath = process.env.DADA_INSTANCE_CONFIG_PATH ?? defaultInstanceConfigPath();
|
||||
if (credentialChannelEnabled) {
|
||||
const clients = initializeApiCredentialClients(await receiveApiCredentials());
|
||||
try {
|
||||
amap = clients.amap;
|
||||
localTestAuth = !clients.resendConfigured;
|
||||
const derivePepper = (purpose: string) => createHmac("sha256", clients.adminAllowlistPepper)
|
||||
.update(`Dada/P0A/${purpose}/v1`, "utf8")
|
||||
.digest();
|
||||
@@ -101,6 +103,7 @@ const app = await createApp({
|
||||
...(browserSupportRelease ? { browserSupportRelease } : {}),
|
||||
...(credits ? { credits } : {}),
|
||||
...(latestExports ? { latestExports } : {}),
|
||||
...(registration && localTestAuth ? { localTestAuth: true } : {}),
|
||||
...(models ? { models } : {}),
|
||||
...(projects ? { projects } : {}),
|
||||
...(registration ? { registration } : {}),
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface RegistrationTransactionEvent {
|
||||
| "registration_send"
|
||||
| "registration_complete"
|
||||
| "registration_send_compensation"
|
||||
| "local_test_session"
|
||||
| "login_send"
|
||||
| "login_complete"
|
||||
| "admin_login_send"
|
||||
@@ -654,6 +655,60 @@ export class RegistrationService {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
createLocalTestSession(): LoginCompleteResult {
|
||||
const now = this.options.clock();
|
||||
return this.runImmediate("local_test_session", () => {
|
||||
const registrationId = "local-test-user-v1";
|
||||
const existing = this.database.prepare(`
|
||||
SELECT user_id, role, status FROM users WHERE registration_id = ?
|
||||
`).get(registrationId) as {
|
||||
role: "user" | "super_admin";
|
||||
status: "active" | "suspended" | "deleted";
|
||||
user_id: string;
|
||||
} | undefined;
|
||||
|
||||
if (existing) {
|
||||
if (existing.role !== "user" || existing.status !== "active") {
|
||||
throw new RegistrationError("AUTH_ENTRY_REJECTED", "account_suspended");
|
||||
}
|
||||
const session = this.insertSession(existing.user_id, "user", now);
|
||||
return {
|
||||
outcome: "committed",
|
||||
value: this.loginResult(this.readCompletedRegistration(existing.user_id, session.sessionId)),
|
||||
};
|
||||
}
|
||||
|
||||
const userId = randomUUID();
|
||||
this.database.prepare(`
|
||||
INSERT INTO users (
|
||||
user_id, normalized_email, role, status, counts_toward_stage_limit,
|
||||
registration_id, created_at
|
||||
) VALUES (?, 'local-test-user@dada.invalid', 'user', 'active', 0, ?, ?)
|
||||
`).run(userId, registrationId, now);
|
||||
this.database.prepare(`
|
||||
INSERT INTO user_profiles (
|
||||
user_id, creator_name, social_id, private_content_notice_version,
|
||||
private_content_notice_acknowledged_at
|
||||
) VALUES (?, '本机测试用户', '@dada_local_test', NULL, NULL)
|
||||
`).run(userId);
|
||||
this.database.prepare(`
|
||||
INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at)
|
||||
VALUES (?, 10, 0, ?)
|
||||
`).run(userId, now);
|
||||
this.database.prepare(`
|
||||
INSERT INTO credit_ledger (
|
||||
ledger_id, user_id, operation_key, entry_type, amount,
|
||||
available_before, available_after, reserved_before, reserved_after, created_at
|
||||
) VALUES (?, ?, 'local-test-registration:v1', 'registration_grant', 10, 0, 10, 0, 0, ?)
|
||||
`).run(randomUUID(), userId, now);
|
||||
const session = this.insertSession(userId, "user", now);
|
||||
return {
|
||||
outcome: "committed",
|
||||
value: this.loginResult(this.readCompletedRegistration(userId, session.sessionId)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
applySecureConfig(candidate: SecureConfigCandidate) {
|
||||
const now = this.options.clock();
|
||||
const fail = (reason: string): never => {
|
||||
|
||||
@@ -27,12 +27,13 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce
|
||||
}
|
||||
|
||||
export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) {
|
||||
try {
|
||||
try {
|
||||
const adminPepper = credentials["Dada/P0A/admin/pepper"];
|
||||
if (!adminPepper) throw new Error("admin_pepper_not_configured");
|
||||
return {
|
||||
adminAllowlistPepper: Buffer.from(adminPepper, "utf8"),
|
||||
amap: credentials["Dada/P0A/api/amap"] ? new RealAmapAdapter(credentials["Dada/P0A/api/amap"]) : new MockAmapAdapter(),
|
||||
resendConfigured: Boolean(credentials["Dada/P0A/api/resend"]),
|
||||
};
|
||||
} finally {
|
||||
for (const name of API_CREDENTIALS) credentials[name] = "";
|
||||
|
||||
Reference in New Issue
Block a user