mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-22 05:06:10 +00:00
feat: viz over user verification types (#3271)
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
-- Copyright (C) 2024-present Puter Technologies Inc.
|
||||
--
|
||||
-- This file is part of Puter.
|
||||
--
|
||||
-- Puter is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as published
|
||||
-- by the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU Affero General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU Affero General Public License
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-- Card fingerprint column. Mirrors SQLite migration 0060. `card_fingerprint`
|
||||
-- is the Stripe card fingerprint (stable per card number) recorded when a user
|
||||
-- clears card verification — the card sibling of `phone`, indexed like it so
|
||||
-- admin tooling can find the accounts that verified with a given card. The card
|
||||
-- itself never touches our DB, only Stripe's fingerprint for it.
|
||||
--
|
||||
-- Idempotent: the column add uses _puter_add_col (defined in mig_1, which
|
||||
-- leaves it resident for later migrations); the index add is guarded against
|
||||
-- INFORMATION_SCHEMA.STATISTICS so the directory replays safely.
|
||||
|
||||
CALL _puter_add_col('user', 'card_fingerprint', '`card_fingerprint` varchar(128) DEFAULT NULL');
|
||||
|
||||
DROP PROCEDURE IF EXISTS _puter_add_user_card_fingerprint_index;
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE _puter_add_user_card_fingerprint_index()
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'user'
|
||||
AND INDEX_NAME = 'idx_user_card_fingerprint'
|
||||
) THEN
|
||||
ALTER TABLE `user` ADD INDEX `idx_user_card_fingerprint` (`card_fingerprint`);
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
|
||||
CALL _puter_add_user_card_fingerprint_index();
|
||||
|
||||
DROP PROCEDURE IF EXISTS _puter_add_user_card_fingerprint_index;
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Copyright (C) 2024-present Puter Technologies Inc.
|
||||
--
|
||||
-- This file is part of Puter.
|
||||
--
|
||||
-- Puter is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as published
|
||||
-- by the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU Affero General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU Affero General Public License
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-- Card fingerprint column. Mirrors SQLite migration 0060. `card_fingerprint`
|
||||
-- is the Stripe card fingerprint (stable per card number) recorded when a user
|
||||
-- clears card verification — the card sibling of `phone`, indexed like it so
|
||||
-- admin tooling can find the accounts that verified with a given card. The card
|
||||
-- itself never touches our DB, only Stripe's fingerprint for it.
|
||||
-- Idempotent via IF NOT EXISTS.
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS card_fingerprint varchar(128);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_card_fingerprint ON "user" (card_fingerprint);
|
||||
@@ -0,0 +1,25 @@
|
||||
-- Copyright (C) 2024-present Puter Technologies Inc.
|
||||
--
|
||||
-- This file is part of Puter.
|
||||
--
|
||||
-- Puter is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as published
|
||||
-- by the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU Affero General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU Affero General Public License
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-- Card fingerprint column. `card_fingerprint` is the Stripe card fingerprint
|
||||
-- (stable per card number) recorded when a user clears card verification — the
|
||||
-- card sibling of `phone`. Indexed like `phone` so admin tooling can find the
|
||||
-- accounts that verified with a given card. The card itself never touches our
|
||||
-- DB, only Stripe's fingerprint for it. Server-only (not on the whoami
|
||||
-- allowlist), so it never reaches clients.
|
||||
ALTER TABLE `user` ADD COLUMN `card_fingerprint` varchar(128) DEFAULT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_user_card_fingerprint ON `user` (`card_fingerprint`);
|
||||
@@ -4086,6 +4086,77 @@ describe('AuthController.handleSignup additional branches', () => {
|
||||
expect(claimed!.password).not.toBeNull();
|
||||
});
|
||||
|
||||
it('claim clears stale phone/card gates on the placeholder when the decision no longer requires them', async () => {
|
||||
// Placeholder seeded with both gates already set. A benign claim
|
||||
// (no validate override → no requirements) must reset them rather
|
||||
// than silently inheriting the stale requirement.
|
||||
const targetEmail = `pseudo_${uniq()}@test.local`;
|
||||
const placeholder = await server.stores.user.create({
|
||||
username: `placeholder_${uniq()}`,
|
||||
uuid: uuidv4(),
|
||||
password: null,
|
||||
email: targetEmail,
|
||||
clean_email: targetEmail,
|
||||
email_confirmed: 0,
|
||||
requires_phone_verification: 1,
|
||||
requires_card_verification: 1,
|
||||
} as never);
|
||||
|
||||
const res = makeRes();
|
||||
await controller.handleSignup(
|
||||
makeReq({
|
||||
username: `claim_${uniq()}`,
|
||||
email: targetEmail,
|
||||
password: 'correct-horse-battery',
|
||||
}),
|
||||
res,
|
||||
);
|
||||
expect(isCompleteLoginResponse(res.body)).toBe(true);
|
||||
|
||||
const claimed = await server.stores.user.getById(placeholder.id, {
|
||||
force: true,
|
||||
});
|
||||
expect(claimed!.requires_phone_verification).toBe(false);
|
||||
expect(claimed!.requires_card_verification).toBe(false);
|
||||
});
|
||||
|
||||
it('claim carries the phone/card gates when the decision requires them', async () => {
|
||||
const targetEmail = `pseudo_${uniq()}@test.local`;
|
||||
const placeholder = await server.stores.user.create({
|
||||
username: `placeholder_${uniq()}`,
|
||||
uuid: uuidv4(),
|
||||
password: null,
|
||||
email: targetEmail,
|
||||
clean_email: targetEmail,
|
||||
email_confirmed: 0,
|
||||
} as never);
|
||||
|
||||
await withSignupValidateOverride(
|
||||
(event) => {
|
||||
event.requires_phone_verification = true;
|
||||
event.requires_card_verification = true;
|
||||
},
|
||||
async () => {
|
||||
const res = makeRes();
|
||||
await controller.handleSignup(
|
||||
makeReq({
|
||||
username: `claim_${uniq()}`,
|
||||
email: targetEmail,
|
||||
password: 'correct-horse-battery',
|
||||
}),
|
||||
res,
|
||||
);
|
||||
expect(isCompleteLoginResponse(res.body)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
const claimed = await server.stores.user.getById(placeholder.id, {
|
||||
force: true,
|
||||
});
|
||||
expect(claimed!.requires_phone_verification).toBe(true);
|
||||
expect(claimed!.requires_card_verification).toBe(true);
|
||||
});
|
||||
|
||||
it('extension hook can require email confirmation via requires_email_confirmation=true', async () => {
|
||||
await withSignupValidateOverride(
|
||||
(event) => {
|
||||
|
||||
@@ -690,24 +690,13 @@ export class AuthController extends PuterController {
|
||||
email_confirm_code,
|
||||
email_confirm_token,
|
||||
email_confirmed: 0,
|
||||
// Pseudo claims always require email confirmation — the
|
||||
// validate hook can only tighten, not loosen, so `1`
|
||||
// stays hardcoded here.
|
||||
requires_email_confirmation: 1,
|
||||
last_activity_ts: signupSqlTs,
|
||||
// Record the v2 reputation from this claim (skip if unset so we
|
||||
// don't clobber an existing score with a placeholder).
|
||||
...(validateEvent.reputation != null
|
||||
? { reputation: validateEvent.reputation }
|
||||
: {}),
|
||||
// Carry the phone gate onto the claimed account when required.
|
||||
...(force_phone_verification
|
||||
? { requires_phone_verification: 1 }
|
||||
: {}),
|
||||
// Likewise for the card gate.
|
||||
...(force_card_verification
|
||||
? { requires_card_verification: 1 }
|
||||
: {}),
|
||||
requires_phone_verification: force_phone_verification ? 1 : 0,
|
||||
requires_card_verification: force_card_verification ? 1 : 0,
|
||||
});
|
||||
|
||||
// Move from temp group to regular user group
|
||||
|
||||
@@ -482,6 +482,9 @@ export class OIDCService extends PuterService {
|
||||
allow: true,
|
||||
no_temp_user: false,
|
||||
requires_email_confirmation: false,
|
||||
requires_phone_verification: false,
|
||||
requires_card_verification: false,
|
||||
reputation: null as number | null,
|
||||
message: null as string | null,
|
||||
code: null as string | null,
|
||||
};
|
||||
@@ -532,6 +535,17 @@ export class OIDCService extends PuterService {
|
||||
};
|
||||
}
|
||||
|
||||
const cfg = this.config as {
|
||||
always_require_phone_verification?: boolean;
|
||||
always_require_card_verification?: boolean;
|
||||
};
|
||||
const force_phone_verification =
|
||||
Boolean(validateEvent.requires_phone_verification) ||
|
||||
Boolean(cfg.always_require_phone_verification);
|
||||
const force_card_verification =
|
||||
Boolean(validateEvent.requires_card_verification) ||
|
||||
Boolean(cfg.always_require_card_verification);
|
||||
|
||||
const created = await this.stores.user.create({
|
||||
username,
|
||||
uuid: uuidv4(),
|
||||
@@ -539,7 +553,14 @@ export class OIDCService extends PuterService {
|
||||
email,
|
||||
clean_email: cleanEmail(email),
|
||||
free_storage: this.config.storage_capacity ?? null,
|
||||
// Email is provider-verified, so the email step is always skipped;
|
||||
// the phone/card gates still apply when the harness flagged them.
|
||||
requires_email_confirmation: false,
|
||||
requires_phone_verification: force_phone_verification,
|
||||
requires_card_verification: force_card_verification,
|
||||
...(validateEvent.reputation != null
|
||||
? { reputation: validateEvent.reputation }
|
||||
: {}),
|
||||
audit_metadata: {
|
||||
ip: clientIp,
|
||||
ip_fwd: proxyIpChain,
|
||||
|
||||
Reference in New Issue
Block a user