|
1 | 1 | import { ActionFunctionArgs } from "@remix-run/server-runtime"; |
2 | 2 | import { typedjson } from "remix-typedjson"; |
3 | 3 | import { z } from "zod"; |
4 | | -import { redirectWithSuccessMessage, typedJsonWithSuccessMessage } from "~/models/message.server"; |
| 4 | +import { redirectWithSuccessMessage, redirectWithErrorMessage, typedJsonWithSuccessMessage } from "~/models/message.server"; |
5 | 5 | import { MultiFactorAuthenticationService } from "~/services/mfa/multiFactorAuthentication.server"; |
6 | 6 | import { requireUserId } from "~/services/session.server"; |
| 7 | +import { ServiceValidationError } from "~/v3/services/baseService.server"; |
7 | 8 | import { useMfaSetup } from "./useMfaSetup"; |
8 | 9 | import { MfaToggle } from "./MfaToggle"; |
9 | 10 | import { MfaSetupDialog } from "./MfaSetupDialog"; |
@@ -49,82 +50,91 @@ function validateForm(formData: FormData) { |
49 | 50 | } |
50 | 51 |
|
51 | 52 | export async function action({ request }: ActionFunctionArgs) { |
52 | | - const userId = await requireUserId(request); |
| 53 | + try { |
| 54 | + const userId = await requireUserId(request); |
53 | 55 |
|
54 | | - const formData = await request.formData(); |
| 56 | + const formData = await request.formData(); |
55 | 57 |
|
56 | | - const submission = validateForm(formData); |
57 | | - |
58 | | - if (!submission.valid) { |
59 | | - return typedjson({ |
60 | | - action: "invalid-form" as const, |
61 | | - errors: submission.errors, |
62 | | - }); |
63 | | - } |
64 | | - |
65 | | - const mfaSetupService = new MultiFactorAuthenticationService(); |
66 | | - |
67 | | - switch (submission.data.action) { |
68 | | - case "enable-mfa": { |
69 | | - const result = await mfaSetupService.enableTotp(userId); |
| 58 | + const submission = validateForm(formData); |
70 | 59 |
|
| 60 | + if (!submission.valid) { |
71 | 61 | return typedjson({ |
72 | | - action: "enable-mfa" as const, |
73 | | - secret: result.secret, |
74 | | - otpAuthUrl: result.otpAuthUrl, |
| 62 | + action: "invalid-form" as const, |
| 63 | + errors: submission.errors, |
75 | 64 | }); |
76 | 65 | } |
77 | | - case "disable-mfa": { |
78 | | - const result = await mfaSetupService.disableTotp(userId, { |
79 | | - totpCode: submission.data.totpCode, |
80 | | - recoveryCode: submission.data.recoveryCode, |
81 | | - }); |
82 | 66 |
|
83 | | - if (result.success) { |
84 | | - return typedJsonWithSuccessMessage( |
85 | | - { |
86 | | - action: "disable-mfa" as const, |
87 | | - success: true as const, |
88 | | - }, |
89 | | - request, |
90 | | - "Successfully disabled MFA" |
91 | | - ); |
92 | | - } else { |
| 67 | + const mfaSetupService = new MultiFactorAuthenticationService(); |
| 68 | + |
| 69 | + switch (submission.data.action) { |
| 70 | + case "enable-mfa": { |
| 71 | + const result = await mfaSetupService.enableTotp(userId); |
| 72 | + |
93 | 73 | return typedjson({ |
94 | | - action: "disable-mfa" as const, |
95 | | - success: false as const, |
96 | | - error: "Invalid code provided. Please try again.", |
| 74 | + action: "enable-mfa" as const, |
| 75 | + secret: result.secret, |
| 76 | + otpAuthUrl: result.otpAuthUrl, |
97 | 77 | }); |
98 | 78 | } |
99 | | - } |
100 | | - case "validate-totp": { |
101 | | - const result = await mfaSetupService.validateTotpSetup(userId, submission.data.totpCode); |
| 79 | + case "disable-mfa": { |
| 80 | + const result = await mfaSetupService.disableTotp(userId, { |
| 81 | + totpCode: submission.data.totpCode, |
| 82 | + recoveryCode: submission.data.recoveryCode, |
| 83 | + }); |
102 | 84 |
|
103 | | - if (result.success) { |
| 85 | + if (result.success) { |
| 86 | + return typedJsonWithSuccessMessage( |
| 87 | + { |
| 88 | + action: "disable-mfa" as const, |
| 89 | + success: true as const, |
| 90 | + }, |
| 91 | + request, |
| 92 | + "Successfully disabled MFA" |
| 93 | + ); |
| 94 | + } else { |
| 95 | + return typedjson({ |
| 96 | + action: "disable-mfa" as const, |
| 97 | + success: false as const, |
| 98 | + error: "Invalid code provided. Please try again.", |
| 99 | + }); |
| 100 | + } |
| 101 | + } |
| 102 | + case "validate-totp": { |
| 103 | + const result = await mfaSetupService.validateTotpSetup(userId, submission.data.totpCode); |
| 104 | + |
| 105 | + if (result.success) { |
| 106 | + return typedjson({ |
| 107 | + action: "validate-totp" as const, |
| 108 | + success: true as const, |
| 109 | + recoveryCodes: result.recoveryCodes, |
| 110 | + }); |
| 111 | + } else { |
| 112 | + return typedjson({ |
| 113 | + action: "validate-totp" as const, |
| 114 | + success: false as const, |
| 115 | + error: "Invalid code provided. Please try again.", |
| 116 | + otpAuthUrl: result.otpAuthUrl, |
| 117 | + secret: result.secret, |
| 118 | + }); |
| 119 | + } |
| 120 | + } |
| 121 | + case "cancel-totp": { |
104 | 122 | return typedjson({ |
105 | | - action: "validate-totp" as const, |
| 123 | + action: "cancel-totp" as const, |
106 | 124 | success: true as const, |
107 | | - recoveryCodes: result.recoveryCodes, |
108 | | - }); |
109 | | - } else { |
110 | | - return typedjson({ |
111 | | - action: "validate-totp" as const, |
112 | | - success: false as const, |
113 | | - error: "Invalid code provided. Please try again.", |
114 | | - otpAuthUrl: result.otpAuthUrl, |
115 | | - secret: result.secret, |
116 | 125 | }); |
117 | 126 | } |
| 127 | + case "saved-recovery-codes": { |
| 128 | + return redirectWithSuccessMessage("/account/security", request, "Successfully enabled MFA"); |
| 129 | + } |
118 | 130 | } |
119 | | - case "cancel-totp": { |
120 | | - return typedjson({ |
121 | | - action: "cancel-totp" as const, |
122 | | - success: true as const, |
123 | | - }); |
124 | | - } |
125 | | - case "saved-recovery-codes": { |
126 | | - return redirectWithSuccessMessage("/account/security", request, "Successfully enabled MFA"); |
| 131 | + } catch (error) { |
| 132 | + if (error instanceof ServiceValidationError) { |
| 133 | + return redirectWithErrorMessage("/account/security", request, error.message); |
127 | 134 | } |
| 135 | + |
| 136 | + // Re-throw unexpected errors |
| 137 | + throw error; |
128 | 138 | } |
129 | 139 | } |
130 | 140 |
|
|
0 commit comments