@arch-cadre/two-factor-email 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.cjs +90 -0
- package/dist/actions.d.ts +8 -0
- package/dist/actions.mjs +83 -0
- package/dist/index.cjs +62 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +64 -0
- package/dist/intl.d.ts +9 -0
- package/dist/routes.cjs +14 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.mjs +9 -0
- package/dist/schema.cjs +34 -0
- package/dist/schema.d.ts +270 -0
- package/dist/schema.mjs +18 -0
- package/dist/ui/settings-toggle.cjs +58 -0
- package/dist/ui/settings-toggle.d.ts +2 -0
- package/dist/ui/settings-toggle.mjs +50 -0
- package/dist/ui/verify-page.cjs +91 -0
- package/dist/ui/verify-page.d.ts +2 -0
- package/dist/ui/verify-page.mjs +85 -0
- package/locales/en/global.json +19 -0
- package/manifest.json +11 -0
- package/package.json +55 -0
package/dist/actions.cjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use server";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.generateAndSendCode = generateAndSendCode;
|
|
8
|
+
exports.is2FAEnabled = is2FAEnabled;
|
|
9
|
+
exports.toggle2FA = toggle2FA;
|
|
10
|
+
exports.verifyAndLogin = verifyAndLogin;
|
|
11
|
+
var _server = require("@arch-cadre/core/server");
|
|
12
|
+
var _drizzleOrm = require("drizzle-orm");
|
|
13
|
+
var _schema = require("./schema.cjs");
|
|
14
|
+
async function is2FAEnabled(userId) {
|
|
15
|
+
const [settings] = await _server.db.select().from(_schema.twoFactorSettingsTable).where((0, _drizzleOrm.eq)(_schema.twoFactorSettingsTable.userId, userId));
|
|
16
|
+
return settings?.enabled ?? false;
|
|
17
|
+
}
|
|
18
|
+
async function toggle2FA(enabled) {
|
|
19
|
+
const {
|
|
20
|
+
user
|
|
21
|
+
} = await (0, _server.getCurrentSession)();
|
|
22
|
+
if (!user) throw new Error("Unauthorized");
|
|
23
|
+
await _server.db.insert(_schema.twoFactorSettingsTable).values({
|
|
24
|
+
userId: user.id,
|
|
25
|
+
enabled
|
|
26
|
+
}).onConflictDoUpdate({
|
|
27
|
+
target: _schema.twoFactorSettingsTable.userId,
|
|
28
|
+
set: {
|
|
29
|
+
enabled,
|
|
30
|
+
updatedAt: /* @__PURE__ */new Date()
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
await _server.eventBus.publish("activity.create", {
|
|
34
|
+
action: "2fa.toggled",
|
|
35
|
+
description: `User ${enabled ? "enabled" : "disabled"} 2FA`,
|
|
36
|
+
userId: user.id,
|
|
37
|
+
metadata: {
|
|
38
|
+
enabled
|
|
39
|
+
}
|
|
40
|
+
}, "auth:2fa");
|
|
41
|
+
return {
|
|
42
|
+
success: true
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function generateAndSendCode(userId) {
|
|
46
|
+
const user = await (0, _server.getUserById)(userId);
|
|
47
|
+
if (!user) throw new Error("User not found");
|
|
48
|
+
const code = Math.floor(1e5 + Math.random() * 9e5).toString();
|
|
49
|
+
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
50
|
+
await _server.db.delete(_schema.twoFactorTokensTable).where((0, _drizzleOrm.eq)(_schema.twoFactorTokensTable.userId, userId));
|
|
51
|
+
await _server.db.insert(_schema.twoFactorTokensTable).values({
|
|
52
|
+
userId,
|
|
53
|
+
code,
|
|
54
|
+
expiresAt
|
|
55
|
+
});
|
|
56
|
+
await (0, _server.send2FACode)(user.email, code);
|
|
57
|
+
await _server.eventBus.publish("activity.create", {
|
|
58
|
+
action: "2fa.sended",
|
|
59
|
+
description: `User sent 2FA code`,
|
|
60
|
+
userId,
|
|
61
|
+
metadata: {
|
|
62
|
+
code
|
|
63
|
+
}
|
|
64
|
+
}, "auth:2fa");
|
|
65
|
+
return {
|
|
66
|
+
success: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function verifyAndLogin(userId, code) {
|
|
70
|
+
const [token] = await _server.db.select().from(_schema.twoFactorTokensTable).where((0, _drizzleOrm.and)((0, _drizzleOrm.eq)(_schema.twoFactorTokensTable.userId, userId), (0, _drizzleOrm.eq)(_schema.twoFactorTokensTable.code, code), (0, _drizzleOrm.gt)(_schema.twoFactorTokensTable.expiresAt, /* @__PURE__ */new Date())));
|
|
71
|
+
if (!token) {
|
|
72
|
+
return {
|
|
73
|
+
error: "Invalid or expired code"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
await _server.db.delete(_schema.twoFactorTokensTable).where((0, _drizzleOrm.eq)(_schema.twoFactorTokensTable.userId, userId));
|
|
77
|
+
const result = await (0, _server.finalizeLogin)(userId, {});
|
|
78
|
+
await _server.eventBus.publish("activity.create", {
|
|
79
|
+
action: "2fa.verified",
|
|
80
|
+
description: `User verified 2FA`,
|
|
81
|
+
userId,
|
|
82
|
+
metadata: {
|
|
83
|
+
userId
|
|
84
|
+
}
|
|
85
|
+
}, "auth:2fa");
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
...result
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function is2FAEnabled(userId: string): Promise<any>;
|
|
2
|
+
export declare function toggle2FA(enabled: boolean): Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function generateAndSendCode(userId: string): Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function verifyAndLogin(userId: string, code: string): Promise<any>;
|
package/dist/actions.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import {
|
|
3
|
+
db,
|
|
4
|
+
eventBus,
|
|
5
|
+
finalizeLogin,
|
|
6
|
+
getCurrentSession,
|
|
7
|
+
getUserById,
|
|
8
|
+
send2FACode
|
|
9
|
+
} from "@arch-cadre/core/server";
|
|
10
|
+
import { and, eq, gt } from "drizzle-orm";
|
|
11
|
+
import { twoFactorSettingsTable, twoFactorTokensTable } from "./schema.mjs";
|
|
12
|
+
export async function is2FAEnabled(userId) {
|
|
13
|
+
const [settings] = await db.select().from(twoFactorSettingsTable).where(eq(twoFactorSettingsTable.userId, userId));
|
|
14
|
+
return settings?.enabled ?? false;
|
|
15
|
+
}
|
|
16
|
+
export async function toggle2FA(enabled) {
|
|
17
|
+
const { user } = await getCurrentSession();
|
|
18
|
+
if (!user) throw new Error("Unauthorized");
|
|
19
|
+
await db.insert(twoFactorSettingsTable).values({ userId: user.id, enabled }).onConflictDoUpdate({
|
|
20
|
+
target: twoFactorSettingsTable.userId,
|
|
21
|
+
set: { enabled, updatedAt: /* @__PURE__ */ new Date() }
|
|
22
|
+
});
|
|
23
|
+
await eventBus.publish(
|
|
24
|
+
"activity.create",
|
|
25
|
+
{
|
|
26
|
+
action: "2fa.toggled",
|
|
27
|
+
description: `User ${enabled ? "enabled" : "disabled"} 2FA`,
|
|
28
|
+
userId: user.id,
|
|
29
|
+
metadata: { enabled }
|
|
30
|
+
},
|
|
31
|
+
"auth:2fa"
|
|
32
|
+
);
|
|
33
|
+
return { success: true };
|
|
34
|
+
}
|
|
35
|
+
export async function generateAndSendCode(userId) {
|
|
36
|
+
const user = await getUserById(userId);
|
|
37
|
+
if (!user) throw new Error("User not found");
|
|
38
|
+
const code = Math.floor(1e5 + Math.random() * 9e5).toString();
|
|
39
|
+
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
40
|
+
await db.delete(twoFactorTokensTable).where(eq(twoFactorTokensTable.userId, userId));
|
|
41
|
+
await db.insert(twoFactorTokensTable).values({
|
|
42
|
+
userId,
|
|
43
|
+
code,
|
|
44
|
+
expiresAt
|
|
45
|
+
});
|
|
46
|
+
await send2FACode(user.email, code);
|
|
47
|
+
await eventBus.publish(
|
|
48
|
+
"activity.create",
|
|
49
|
+
{
|
|
50
|
+
action: "2fa.sended",
|
|
51
|
+
description: `User sent 2FA code`,
|
|
52
|
+
userId,
|
|
53
|
+
metadata: { code }
|
|
54
|
+
},
|
|
55
|
+
"auth:2fa"
|
|
56
|
+
);
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
export async function verifyAndLogin(userId, code) {
|
|
60
|
+
const [token] = await db.select().from(twoFactorTokensTable).where(
|
|
61
|
+
and(
|
|
62
|
+
eq(twoFactorTokensTable.userId, userId),
|
|
63
|
+
eq(twoFactorTokensTable.code, code),
|
|
64
|
+
gt(twoFactorTokensTable.expiresAt, /* @__PURE__ */ new Date())
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
if (!token) {
|
|
68
|
+
return { error: "Invalid or expired code" };
|
|
69
|
+
}
|
|
70
|
+
await db.delete(twoFactorTokensTable).where(eq(twoFactorTokensTable.userId, userId));
|
|
71
|
+
const result = await finalizeLogin(userId, {});
|
|
72
|
+
await eventBus.publish(
|
|
73
|
+
"activity.create",
|
|
74
|
+
{
|
|
75
|
+
action: "2fa.verified",
|
|
76
|
+
description: `User verified 2FA`,
|
|
77
|
+
userId,
|
|
78
|
+
metadata: { userId }
|
|
79
|
+
},
|
|
80
|
+
"auth:2fa"
|
|
81
|
+
);
|
|
82
|
+
return { success: true, ...result };
|
|
83
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _server = require("@arch-cadre/core/server");
|
|
8
|
+
var _drizzleOrm = require("drizzle-orm");
|
|
9
|
+
var _manifest = _interopRequireDefault(require("../manifest.json"));
|
|
10
|
+
var _actions = require("./actions.cjs");
|
|
11
|
+
var _routes = require("./routes.cjs");
|
|
12
|
+
var _settingsToggle = require("./ui/settings-toggle.cjs");
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
const twoFactorEmailModule = {
|
|
15
|
+
manifest: _manifest.default,
|
|
16
|
+
routes: {
|
|
17
|
+
public: _routes.publicRoutes
|
|
18
|
+
},
|
|
19
|
+
init: async () => {
|
|
20
|
+
console.log("[Module:2FA-Email] Initializing...");
|
|
21
|
+
(0, _server.registerAuthValidator)(async userId => {
|
|
22
|
+
console.log(`[Module:2FA-Email] Validating login for user: ${userId}`);
|
|
23
|
+
try {
|
|
24
|
+
const enabled = await (0, _actions.is2FAEnabled)(userId);
|
|
25
|
+
console.log(`[Module:2FA-Email] 2FA enabled for user ${userId}: ${enabled}`);
|
|
26
|
+
if (enabled) {
|
|
27
|
+
console.log(`[Module:2FA-Email] Generating code for user ${userId}`);
|
|
28
|
+
await (0, _actions.generateAndSendCode)(userId);
|
|
29
|
+
return {
|
|
30
|
+
status: "CHALLENGE_REQUIRED",
|
|
31
|
+
type: "email_2fa",
|
|
32
|
+
userId,
|
|
33
|
+
redirect: `/auth/2fa?userId=${userId}`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("[Module:2FA-Email] Error during auth validation:", error);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
console.log("[Module:2FA-Email] Validator registered.");
|
|
42
|
+
},
|
|
43
|
+
onDisable: async () => {
|
|
44
|
+
console.log("[Module:2FA-Email] onDisable: Dropping all tables physically...");
|
|
45
|
+
try {
|
|
46
|
+
const tables = ["two_factor_settings", "two_factor_tokens"];
|
|
47
|
+
for (const table of tables) {
|
|
48
|
+
await _server.db.execute(_drizzleOrm.sql.raw(`DROP TABLE IF EXISTS ${table} CASCADE`));
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error("[Module:2FA-Email] onDisable Error:", e);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
extensions: [{
|
|
55
|
+
id: "2fa-settings-section",
|
|
56
|
+
targetModule: "user-profile",
|
|
57
|
+
point: "settings:extra-sections",
|
|
58
|
+
component: _settingsToggle.TwoFactorSettings,
|
|
59
|
+
priority: 50
|
|
60
|
+
}]
|
|
61
|
+
};
|
|
62
|
+
module.exports = twoFactorEmailModule;
|
package/dist/index.d.ts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { db, registerAuthValidator } from "@arch-cadre/core/server";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import manifest from "../manifest.json";
|
|
4
|
+
import { generateAndSendCode, is2FAEnabled } from "./actions.mjs";
|
|
5
|
+
import { publicRoutes } from "./routes.mjs";
|
|
6
|
+
import { TwoFactorSettings } from "./ui/settings-toggle.mjs";
|
|
7
|
+
const twoFactorEmailModule = {
|
|
8
|
+
manifest,
|
|
9
|
+
routes: {
|
|
10
|
+
public: publicRoutes
|
|
11
|
+
},
|
|
12
|
+
init: async () => {
|
|
13
|
+
console.log("[Module:2FA-Email] Initializing...");
|
|
14
|
+
registerAuthValidator(async (userId) => {
|
|
15
|
+
console.log(`[Module:2FA-Email] Validating login for user: ${userId}`);
|
|
16
|
+
try {
|
|
17
|
+
const enabled = await is2FAEnabled(userId);
|
|
18
|
+
console.log(
|
|
19
|
+
`[Module:2FA-Email] 2FA enabled for user ${userId}: ${enabled}`
|
|
20
|
+
);
|
|
21
|
+
if (enabled) {
|
|
22
|
+
console.log(`[Module:2FA-Email] Generating code for user ${userId}`);
|
|
23
|
+
await generateAndSendCode(userId);
|
|
24
|
+
return {
|
|
25
|
+
status: "CHALLENGE_REQUIRED",
|
|
26
|
+
type: "email_2fa",
|
|
27
|
+
userId,
|
|
28
|
+
redirect: `/auth/2fa?userId=${userId}`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(
|
|
33
|
+
"[Module:2FA-Email] Error during auth validation:",
|
|
34
|
+
error
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
});
|
|
39
|
+
console.log("[Module:2FA-Email] Validator registered.");
|
|
40
|
+
},
|
|
41
|
+
onDisable: async () => {
|
|
42
|
+
console.log(
|
|
43
|
+
"[Module:2FA-Email] onDisable: Dropping all tables physically..."
|
|
44
|
+
);
|
|
45
|
+
try {
|
|
46
|
+
const tables = ["two_factor_settings", "two_factor_tokens"];
|
|
47
|
+
for (const table of tables) {
|
|
48
|
+
await db.execute(sql.raw(`DROP TABLE IF EXISTS ${table} CASCADE`));
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error("[Module:2FA-Email] onDisable Error:", e);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
extensions: [
|
|
55
|
+
{
|
|
56
|
+
id: "2fa-settings-section",
|
|
57
|
+
targetModule: "user-profile",
|
|
58
|
+
point: "settings:extra-sections",
|
|
59
|
+
component: TwoFactorSettings,
|
|
60
|
+
priority: 50
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
export default twoFactorEmailModule;
|
package/dist/intl.d.ts
ADDED
package/dist/routes.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.publicRoutes = void 0;
|
|
7
|
+
var _dynamic = _interopRequireDefault(require("next/dynamic"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
const TwoFactorVerifyPage = (0, _dynamic.default)(() => Promise.resolve().then(() => require("./ui/verify-page.cjs")));
|
|
10
|
+
const publicRoutes = exports.publicRoutes = [{
|
|
11
|
+
path: "/auth/2fa",
|
|
12
|
+
component: TwoFactorVerifyPage,
|
|
13
|
+
auth: false
|
|
14
|
+
}];
|
package/dist/routes.d.ts
ADDED
package/dist/routes.mjs
ADDED
package/dist/schema.cjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.twoFactorTokensTable = exports.twoFactorSettingsTable = exports.twoFactorSchema = void 0;
|
|
7
|
+
var _core = require("@arch-cadre/core");
|
|
8
|
+
var _pgCore = require("drizzle-orm/pg-core");
|
|
9
|
+
const twoFactorSettingsTable = exports.twoFactorSettingsTable = (0, _pgCore.pgTable)("two_factor_settings", {
|
|
10
|
+
userId: (0, _pgCore.text)("user_id").primaryKey().references(() => _core.userTable.id, {
|
|
11
|
+
onDelete: "cascade"
|
|
12
|
+
}),
|
|
13
|
+
enabled: (0, _pgCore.boolean)("enabled").notNull().default(false),
|
|
14
|
+
updatedAt: (0, _pgCore.timestamp)("updated_at", {
|
|
15
|
+
precision: 3
|
|
16
|
+
}).defaultNow()
|
|
17
|
+
});
|
|
18
|
+
const twoFactorTokensTable = exports.twoFactorTokensTable = (0, _pgCore.pgTable)("two_factor_tokens", {
|
|
19
|
+
id: (0, _pgCore.text)("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
|
|
20
|
+
userId: (0, _pgCore.text)("user_id").notNull().references(() => _core.userTable.id, {
|
|
21
|
+
onDelete: "cascade"
|
|
22
|
+
}),
|
|
23
|
+
code: (0, _pgCore.text)("code").notNull(),
|
|
24
|
+
expiresAt: (0, _pgCore.timestamp)("expires_at", {
|
|
25
|
+
precision: 3
|
|
26
|
+
}).notNull(),
|
|
27
|
+
createdAt: (0, _pgCore.timestamp)("created_at", {
|
|
28
|
+
precision: 3
|
|
29
|
+
}).notNull().defaultNow()
|
|
30
|
+
});
|
|
31
|
+
const twoFactorSchema = exports.twoFactorSchema = {
|
|
32
|
+
twoFactorSettingsTable,
|
|
33
|
+
twoFactorTokensTable
|
|
34
|
+
};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
export declare const twoFactorSettingsTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
2
|
+
name: "two_factor_settings";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
userId: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
6
|
+
name: string;
|
|
7
|
+
tableName: "two_factor_settings";
|
|
8
|
+
dataType: "string";
|
|
9
|
+
data: string;
|
|
10
|
+
driverParam: string;
|
|
11
|
+
notNull: true;
|
|
12
|
+
hasDefault: false;
|
|
13
|
+
isPrimaryKey: false;
|
|
14
|
+
isAutoincrement: false;
|
|
15
|
+
hasRuntimeDefault: false;
|
|
16
|
+
enumValues: undefined;
|
|
17
|
+
identity: undefined;
|
|
18
|
+
generated: undefined;
|
|
19
|
+
}>;
|
|
20
|
+
enabled: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBooleanBuilder>>, {
|
|
21
|
+
name: string;
|
|
22
|
+
tableName: "two_factor_settings";
|
|
23
|
+
dataType: "boolean";
|
|
24
|
+
data: boolean;
|
|
25
|
+
driverParam: boolean;
|
|
26
|
+
notNull: true;
|
|
27
|
+
hasDefault: true;
|
|
28
|
+
isPrimaryKey: false;
|
|
29
|
+
isAutoincrement: false;
|
|
30
|
+
hasRuntimeDefault: false;
|
|
31
|
+
enumValues: undefined;
|
|
32
|
+
identity: undefined;
|
|
33
|
+
generated: undefined;
|
|
34
|
+
}>;
|
|
35
|
+
updatedAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
36
|
+
name: string;
|
|
37
|
+
tableName: "two_factor_settings";
|
|
38
|
+
dataType: "object date";
|
|
39
|
+
data: Date;
|
|
40
|
+
driverParam: string;
|
|
41
|
+
notNull: false;
|
|
42
|
+
hasDefault: true;
|
|
43
|
+
isPrimaryKey: false;
|
|
44
|
+
isAutoincrement: false;
|
|
45
|
+
hasRuntimeDefault: false;
|
|
46
|
+
enumValues: undefined;
|
|
47
|
+
identity: undefined;
|
|
48
|
+
generated: undefined;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
dialect: "pg";
|
|
52
|
+
}>;
|
|
53
|
+
export declare const twoFactorTokensTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
54
|
+
name: "two_factor_tokens";
|
|
55
|
+
schema: undefined;
|
|
56
|
+
columns: {
|
|
57
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").SetHasRuntimeDefault<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>>>, {
|
|
58
|
+
name: string;
|
|
59
|
+
tableName: "two_factor_tokens";
|
|
60
|
+
dataType: "string";
|
|
61
|
+
data: string;
|
|
62
|
+
driverParam: string;
|
|
63
|
+
notNull: true;
|
|
64
|
+
hasDefault: true;
|
|
65
|
+
isPrimaryKey: false;
|
|
66
|
+
isAutoincrement: false;
|
|
67
|
+
hasRuntimeDefault: false;
|
|
68
|
+
enumValues: undefined;
|
|
69
|
+
identity: undefined;
|
|
70
|
+
generated: undefined;
|
|
71
|
+
}>;
|
|
72
|
+
userId: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
73
|
+
name: string;
|
|
74
|
+
tableName: "two_factor_tokens";
|
|
75
|
+
dataType: "string";
|
|
76
|
+
data: string;
|
|
77
|
+
driverParam: string;
|
|
78
|
+
notNull: true;
|
|
79
|
+
hasDefault: false;
|
|
80
|
+
isPrimaryKey: false;
|
|
81
|
+
isAutoincrement: false;
|
|
82
|
+
hasRuntimeDefault: false;
|
|
83
|
+
enumValues: undefined;
|
|
84
|
+
identity: undefined;
|
|
85
|
+
generated: undefined;
|
|
86
|
+
}>;
|
|
87
|
+
code: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
88
|
+
name: string;
|
|
89
|
+
tableName: "two_factor_tokens";
|
|
90
|
+
dataType: "string";
|
|
91
|
+
data: string;
|
|
92
|
+
driverParam: string;
|
|
93
|
+
notNull: true;
|
|
94
|
+
hasDefault: false;
|
|
95
|
+
isPrimaryKey: false;
|
|
96
|
+
isAutoincrement: false;
|
|
97
|
+
hasRuntimeDefault: false;
|
|
98
|
+
enumValues: undefined;
|
|
99
|
+
identity: undefined;
|
|
100
|
+
generated: undefined;
|
|
101
|
+
}>;
|
|
102
|
+
expiresAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
103
|
+
name: string;
|
|
104
|
+
tableName: "two_factor_tokens";
|
|
105
|
+
dataType: "object date";
|
|
106
|
+
data: Date;
|
|
107
|
+
driverParam: string;
|
|
108
|
+
notNull: true;
|
|
109
|
+
hasDefault: false;
|
|
110
|
+
isPrimaryKey: false;
|
|
111
|
+
isAutoincrement: false;
|
|
112
|
+
hasRuntimeDefault: false;
|
|
113
|
+
enumValues: undefined;
|
|
114
|
+
identity: undefined;
|
|
115
|
+
generated: undefined;
|
|
116
|
+
}>;
|
|
117
|
+
createdAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>, {
|
|
118
|
+
name: string;
|
|
119
|
+
tableName: "two_factor_tokens";
|
|
120
|
+
dataType: "object date";
|
|
121
|
+
data: Date;
|
|
122
|
+
driverParam: string;
|
|
123
|
+
notNull: true;
|
|
124
|
+
hasDefault: true;
|
|
125
|
+
isPrimaryKey: false;
|
|
126
|
+
isAutoincrement: false;
|
|
127
|
+
hasRuntimeDefault: false;
|
|
128
|
+
enumValues: undefined;
|
|
129
|
+
identity: undefined;
|
|
130
|
+
generated: undefined;
|
|
131
|
+
}>;
|
|
132
|
+
};
|
|
133
|
+
dialect: "pg";
|
|
134
|
+
}>;
|
|
135
|
+
export declare const twoFactorSchema: {
|
|
136
|
+
twoFactorSettingsTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
137
|
+
name: "two_factor_settings";
|
|
138
|
+
schema: undefined;
|
|
139
|
+
columns: {
|
|
140
|
+
userId: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
141
|
+
name: string;
|
|
142
|
+
tableName: "two_factor_settings";
|
|
143
|
+
dataType: "string";
|
|
144
|
+
data: string;
|
|
145
|
+
driverParam: string;
|
|
146
|
+
notNull: true;
|
|
147
|
+
hasDefault: false;
|
|
148
|
+
isPrimaryKey: false;
|
|
149
|
+
isAutoincrement: false;
|
|
150
|
+
hasRuntimeDefault: false;
|
|
151
|
+
enumValues: undefined;
|
|
152
|
+
identity: undefined;
|
|
153
|
+
generated: undefined;
|
|
154
|
+
}>;
|
|
155
|
+
enabled: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBooleanBuilder>>, {
|
|
156
|
+
name: string;
|
|
157
|
+
tableName: "two_factor_settings";
|
|
158
|
+
dataType: "boolean";
|
|
159
|
+
data: boolean;
|
|
160
|
+
driverParam: boolean;
|
|
161
|
+
notNull: true;
|
|
162
|
+
hasDefault: true;
|
|
163
|
+
isPrimaryKey: false;
|
|
164
|
+
isAutoincrement: false;
|
|
165
|
+
hasRuntimeDefault: false;
|
|
166
|
+
enumValues: undefined;
|
|
167
|
+
identity: undefined;
|
|
168
|
+
generated: undefined;
|
|
169
|
+
}>;
|
|
170
|
+
updatedAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_settings", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
171
|
+
name: string;
|
|
172
|
+
tableName: "two_factor_settings";
|
|
173
|
+
dataType: "object date";
|
|
174
|
+
data: Date;
|
|
175
|
+
driverParam: string;
|
|
176
|
+
notNull: false;
|
|
177
|
+
hasDefault: true;
|
|
178
|
+
isPrimaryKey: false;
|
|
179
|
+
isAutoincrement: false;
|
|
180
|
+
hasRuntimeDefault: false;
|
|
181
|
+
enumValues: undefined;
|
|
182
|
+
identity: undefined;
|
|
183
|
+
generated: undefined;
|
|
184
|
+
}>;
|
|
185
|
+
};
|
|
186
|
+
dialect: "pg";
|
|
187
|
+
}>;
|
|
188
|
+
twoFactorTokensTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
189
|
+
name: "two_factor_tokens";
|
|
190
|
+
schema: undefined;
|
|
191
|
+
columns: {
|
|
192
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").SetHasRuntimeDefault<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>>>, {
|
|
193
|
+
name: string;
|
|
194
|
+
tableName: "two_factor_tokens";
|
|
195
|
+
dataType: "string";
|
|
196
|
+
data: string;
|
|
197
|
+
driverParam: string;
|
|
198
|
+
notNull: true;
|
|
199
|
+
hasDefault: true;
|
|
200
|
+
isPrimaryKey: false;
|
|
201
|
+
isAutoincrement: false;
|
|
202
|
+
hasRuntimeDefault: false;
|
|
203
|
+
enumValues: undefined;
|
|
204
|
+
identity: undefined;
|
|
205
|
+
generated: undefined;
|
|
206
|
+
}>;
|
|
207
|
+
userId: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
208
|
+
name: string;
|
|
209
|
+
tableName: "two_factor_tokens";
|
|
210
|
+
dataType: "string";
|
|
211
|
+
data: string;
|
|
212
|
+
driverParam: string;
|
|
213
|
+
notNull: true;
|
|
214
|
+
hasDefault: false;
|
|
215
|
+
isPrimaryKey: false;
|
|
216
|
+
isAutoincrement: false;
|
|
217
|
+
hasRuntimeDefault: false;
|
|
218
|
+
enumValues: undefined;
|
|
219
|
+
identity: undefined;
|
|
220
|
+
generated: undefined;
|
|
221
|
+
}>;
|
|
222
|
+
code: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
223
|
+
name: string;
|
|
224
|
+
tableName: "two_factor_tokens";
|
|
225
|
+
dataType: "string";
|
|
226
|
+
data: string;
|
|
227
|
+
driverParam: string;
|
|
228
|
+
notNull: true;
|
|
229
|
+
hasDefault: false;
|
|
230
|
+
isPrimaryKey: false;
|
|
231
|
+
isAutoincrement: false;
|
|
232
|
+
hasRuntimeDefault: false;
|
|
233
|
+
enumValues: undefined;
|
|
234
|
+
identity: undefined;
|
|
235
|
+
generated: undefined;
|
|
236
|
+
}>;
|
|
237
|
+
expiresAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
238
|
+
name: string;
|
|
239
|
+
tableName: "two_factor_tokens";
|
|
240
|
+
dataType: "object date";
|
|
241
|
+
data: Date;
|
|
242
|
+
driverParam: string;
|
|
243
|
+
notNull: true;
|
|
244
|
+
hasDefault: false;
|
|
245
|
+
isPrimaryKey: false;
|
|
246
|
+
isAutoincrement: false;
|
|
247
|
+
hasRuntimeDefault: false;
|
|
248
|
+
enumValues: undefined;
|
|
249
|
+
identity: undefined;
|
|
250
|
+
generated: undefined;
|
|
251
|
+
}>;
|
|
252
|
+
createdAt: import("drizzle-orm/pg-core").PgBuildColumn<"two_factor_tokens", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>, {
|
|
253
|
+
name: string;
|
|
254
|
+
tableName: "two_factor_tokens";
|
|
255
|
+
dataType: "object date";
|
|
256
|
+
data: Date;
|
|
257
|
+
driverParam: string;
|
|
258
|
+
notNull: true;
|
|
259
|
+
hasDefault: true;
|
|
260
|
+
isPrimaryKey: false;
|
|
261
|
+
isAutoincrement: false;
|
|
262
|
+
hasRuntimeDefault: false;
|
|
263
|
+
enumValues: undefined;
|
|
264
|
+
identity: undefined;
|
|
265
|
+
generated: undefined;
|
|
266
|
+
}>;
|
|
267
|
+
};
|
|
268
|
+
dialect: "pg";
|
|
269
|
+
}>;
|
|
270
|
+
};
|
package/dist/schema.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { userTable } from "@arch-cadre/core";
|
|
2
|
+
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
3
|
+
export const twoFactorSettingsTable = pgTable("two_factor_settings", {
|
|
4
|
+
userId: text("user_id").primaryKey().references(() => userTable.id, { onDelete: "cascade" }),
|
|
5
|
+
enabled: boolean("enabled").notNull().default(false),
|
|
6
|
+
updatedAt: timestamp("updated_at", { precision: 3 }).defaultNow()
|
|
7
|
+
});
|
|
8
|
+
export const twoFactorTokensTable = pgTable("two_factor_tokens", {
|
|
9
|
+
id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
|
|
10
|
+
userId: text("user_id").notNull().references(() => userTable.id, { onDelete: "cascade" }),
|
|
11
|
+
code: text("code").notNull(),
|
|
12
|
+
expiresAt: timestamp("expires_at", { precision: 3 }).notNull(),
|
|
13
|
+
createdAt: timestamp("created_at", { precision: 3 }).notNull().defaultNow()
|
|
14
|
+
});
|
|
15
|
+
export const twoFactorSchema = {
|
|
16
|
+
twoFactorSettingsTable,
|
|
17
|
+
twoFactorTokensTable
|
|
18
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.TwoFactorSettings = TwoFactorSettings;
|
|
8
|
+
var _intl = require("@arch-cadre/intl");
|
|
9
|
+
var _card = require("@arch-cadre/ui/components/card");
|
|
10
|
+
var _label = require("@arch-cadre/ui/components/label");
|
|
11
|
+
var _switch = require("@arch-cadre/ui/components/switch");
|
|
12
|
+
var _useUser = require("@arch-cadre/ui/hooks/use-user");
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
+
var React = _react;
|
|
15
|
+
var _sonner = require("sonner");
|
|
16
|
+
var _actions = require("../actions.cjs");
|
|
17
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
18
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
19
|
+
function TwoFactorSettings() {
|
|
20
|
+
const {
|
|
21
|
+
user
|
|
22
|
+
} = (0, _useUser.useUser)();
|
|
23
|
+
const {
|
|
24
|
+
t
|
|
25
|
+
} = (0, _intl.useTranslation)();
|
|
26
|
+
const [enabled, setEnabled] = (0, _react.useState)(false);
|
|
27
|
+
const [loading, setLoading] = (0, _react.useState)(true);
|
|
28
|
+
(0, _react.useEffect)(() => {
|
|
29
|
+
if (user) {
|
|
30
|
+
(0, _actions.is2FAEnabled)(user.id).then(val => {
|
|
31
|
+
setEnabled(val);
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}, [user]);
|
|
36
|
+
const handleToggle = async val => {
|
|
37
|
+
try {
|
|
38
|
+
await (0, _actions.toggle2FA)(val);
|
|
39
|
+
setEnabled(val);
|
|
40
|
+
_sonner.toast.success(val ? t("2FA enabled") : t("2FA disabled"));
|
|
41
|
+
} catch {
|
|
42
|
+
_sonner.toast.error(t("Action failed"));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
if (loading) return null;
|
|
46
|
+
return /* @__PURE__ */React.createElement(_card.Card, null, /* @__PURE__ */React.createElement(_card.CardHeader, null, /* @__PURE__ */React.createElement(_card.CardTitle, null, t("Two Factor Authentication")), /* @__PURE__ */React.createElement(_card.CardDescription, null, t("Add an extra layer of security to your account by requiring a code sent to your email."))), /* @__PURE__ */React.createElement(_card.CardContent, {
|
|
47
|
+
className: "flex items-center justify-between"
|
|
48
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
49
|
+
htmlFor: "2fa-toggle",
|
|
50
|
+
className: "flex flex-col "
|
|
51
|
+
}, /* @__PURE__ */React.createElement("span", null, t("Email Authentication")), /* @__PURE__ */React.createElement("span", {
|
|
52
|
+
className: "font-normal text-xs text-muted-foreground"
|
|
53
|
+
}, enabled ? t("Currently enabled") : t("Currently disabled"))), /* @__PURE__ */React.createElement(_switch.Switch, {
|
|
54
|
+
id: "2fa-toggle",
|
|
55
|
+
checked: enabled,
|
|
56
|
+
onCheckedChange: handleToggle
|
|
57
|
+
})));
|
|
58
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle
|
|
9
|
+
} from "@arch-cadre/ui/components/card";
|
|
10
|
+
import { Label } from "@arch-cadre/ui/components/label";
|
|
11
|
+
import { Switch } from "@arch-cadre/ui/components/switch";
|
|
12
|
+
import { useUser } from "@arch-cadre/ui/hooks/use-user";
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import { useEffect, useState } from "react";
|
|
15
|
+
import { toast } from "sonner";
|
|
16
|
+
import { is2FAEnabled, toggle2FA } from "../actions.mjs";
|
|
17
|
+
export function TwoFactorSettings() {
|
|
18
|
+
const { user } = useUser();
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const [enabled, setEnabled] = useState(false);
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (user) {
|
|
24
|
+
is2FAEnabled(user.id).then((val) => {
|
|
25
|
+
setEnabled(val);
|
|
26
|
+
setLoading(false);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}, [user]);
|
|
30
|
+
const handleToggle = async (val) => {
|
|
31
|
+
try {
|
|
32
|
+
await toggle2FA(val);
|
|
33
|
+
setEnabled(val);
|
|
34
|
+
toast.success(val ? t("2FA enabled") : t("2FA disabled"));
|
|
35
|
+
} catch {
|
|
36
|
+
toast.error(t("Action failed"));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
if (loading) return null;
|
|
40
|
+
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, null, /* @__PURE__ */ React.createElement(CardTitle, null, t("Two Factor Authentication")), /* @__PURE__ */ React.createElement(CardDescription, null, t(
|
|
41
|
+
"Add an extra layer of security to your account by requiring a code sent to your email."
|
|
42
|
+
))), /* @__PURE__ */ React.createElement(CardContent, { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "2fa-toggle", className: "flex flex-col " }, /* @__PURE__ */ React.createElement("span", null, t("Email Authentication")), /* @__PURE__ */ React.createElement("span", { className: "font-normal text-xs text-muted-foreground" }, enabled ? t("Currently enabled") : t("Currently disabled"))), /* @__PURE__ */ React.createElement(
|
|
43
|
+
Switch,
|
|
44
|
+
{
|
|
45
|
+
id: "2fa-toggle",
|
|
46
|
+
checked: enabled,
|
|
47
|
+
onCheckedChange: handleToggle
|
|
48
|
+
}
|
|
49
|
+
)));
|
|
50
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
module.exports = TwoFactorVerifyPage;
|
|
8
|
+
var _intl = require("@arch-cadre/intl");
|
|
9
|
+
var _button = require("@arch-cadre/ui/components/button");
|
|
10
|
+
var _card = require("@arch-cadre/ui/components/card");
|
|
11
|
+
var _input = require("@arch-cadre/ui/components/input");
|
|
12
|
+
var _loader = require("@arch-cadre/ui/shared/loader");
|
|
13
|
+
var _navigation = require("next/navigation");
|
|
14
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
15
|
+
var React = _react;
|
|
16
|
+
var _sonner = require("sonner");
|
|
17
|
+
var _actions = require("../actions.cjs");
|
|
18
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
19
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
20
|
+
function TwoFactorVerifyPage() {
|
|
21
|
+
const {
|
|
22
|
+
t
|
|
23
|
+
} = (0, _intl.useTranslation)();
|
|
24
|
+
const router = (0, _navigation.useRouter)();
|
|
25
|
+
const searchParams = (0, _navigation.useSearchParams)();
|
|
26
|
+
const userId = searchParams.get("userId");
|
|
27
|
+
const [code, setCode] = (0, _react.useState)("");
|
|
28
|
+
const [isSubmitting, setIsSubmitting] = (0, _react.useState)(false);
|
|
29
|
+
if (!userId) {
|
|
30
|
+
router.push("/signin");
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const handleSubmit = async e => {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
setIsSubmitting(true);
|
|
36
|
+
try {
|
|
37
|
+
const result = await (0, _actions.verifyAndLogin)(userId, code);
|
|
38
|
+
if (result.error === null) {
|
|
39
|
+
_sonner.toast.success(t("Logged in successfully"));
|
|
40
|
+
window.location.href = "/";
|
|
41
|
+
} else {
|
|
42
|
+
_sonner.toast.error(result.error || t("Verification failed"));
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
_sonner.toast.error(t("An error occurred"));
|
|
46
|
+
} finally {
|
|
47
|
+
setIsSubmitting(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handleResend = async () => {
|
|
51
|
+
try {
|
|
52
|
+
await (0, _actions.generateAndSendCode)(userId);
|
|
53
|
+
_sonner.toast.success(t("New code sent to your email"));
|
|
54
|
+
} catch {
|
|
55
|
+
_sonner.toast.error(t("Failed to send code"));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return /* @__PURE__ */React.createElement("div", {
|
|
59
|
+
className: "flex min-h-screen items-center justify-center bg-muted/50 p-4"
|
|
60
|
+
}, /* @__PURE__ */React.createElement(_card.Card, {
|
|
61
|
+
className: "w-full max-w-md"
|
|
62
|
+
}, /* @__PURE__ */React.createElement(_card.CardHeader, {
|
|
63
|
+
className: "text-center"
|
|
64
|
+
}, /* @__PURE__ */React.createElement(_card.CardTitle, {
|
|
65
|
+
className: "text-2xl"
|
|
66
|
+
}, t("Two Factor Verification")), /* @__PURE__ */React.createElement(_card.CardDescription, null, t("Enter the 6-digit code sent to your email address to continue."))), /* @__PURE__ */React.createElement(_card.CardContent, null, /* @__PURE__ */React.createElement("form", {
|
|
67
|
+
onSubmit: handleSubmit,
|
|
68
|
+
className: "space-y-4"
|
|
69
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
70
|
+
className: "space-y-2"
|
|
71
|
+
}, /* @__PURE__ */React.createElement(_input.Input, {
|
|
72
|
+
type: "text",
|
|
73
|
+
maxLength: 6,
|
|
74
|
+
value: code,
|
|
75
|
+
onChange: e => setCode(e.target.value.replace(/\D/g, "")),
|
|
76
|
+
placeholder: "000000",
|
|
77
|
+
className: "text-center text-2xl tracking-[0.5em] font-mono h-14",
|
|
78
|
+
autoFocus: true
|
|
79
|
+
})), /* @__PURE__ */React.createElement(_button.Button, {
|
|
80
|
+
type: "submit",
|
|
81
|
+
className: "w-full h-11",
|
|
82
|
+
disabled: isSubmitting || code.length !== 6
|
|
83
|
+
}, isSubmitting ? /* @__PURE__ */React.createElement(_loader.Loader, {
|
|
84
|
+
variant: "dark"
|
|
85
|
+
}) : t("Verify & Signin")), /* @__PURE__ */React.createElement(_button.Button, {
|
|
86
|
+
type: "button",
|
|
87
|
+
variant: "ghost",
|
|
88
|
+
className: "w-full",
|
|
89
|
+
onClick: handleResend
|
|
90
|
+
}, t("Didn't receive a code? Resend"))))));
|
|
91
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import { Button } from "@arch-cadre/ui/components/button";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle
|
|
10
|
+
} from "@arch-cadre/ui/components/card";
|
|
11
|
+
import { Input } from "@arch-cadre/ui/components/input";
|
|
12
|
+
import { Loader } from "@arch-cadre/ui/shared/loader";
|
|
13
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
import { toast } from "sonner";
|
|
17
|
+
import { generateAndSendCode, verifyAndLogin } from "../actions.mjs";
|
|
18
|
+
export default function TwoFactorVerifyPage() {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const searchParams = useSearchParams();
|
|
22
|
+
const userId = searchParams.get("userId");
|
|
23
|
+
const [code, setCode] = useState("");
|
|
24
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
25
|
+
if (!userId) {
|
|
26
|
+
router.push("/signin");
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const handleSubmit = async (e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
setIsSubmitting(true);
|
|
32
|
+
try {
|
|
33
|
+
const result = await verifyAndLogin(userId, code);
|
|
34
|
+
if (result.error === null) {
|
|
35
|
+
toast.success(t("Logged in successfully"));
|
|
36
|
+
window.location.href = "/";
|
|
37
|
+
} else {
|
|
38
|
+
toast.error(result.error || t("Verification failed"));
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
toast.error(t("An error occurred"));
|
|
42
|
+
} finally {
|
|
43
|
+
setIsSubmitting(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleResend = async () => {
|
|
47
|
+
try {
|
|
48
|
+
await generateAndSendCode(userId);
|
|
49
|
+
toast.success(t("New code sent to your email"));
|
|
50
|
+
} catch {
|
|
51
|
+
toast.error(t("Failed to send code"));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return /* @__PURE__ */ React.createElement("div", { className: "flex min-h-screen items-center justify-center bg-muted/50 p-4" }, /* @__PURE__ */ React.createElement(Card, { className: "w-full max-w-md" }, /* @__PURE__ */ React.createElement(CardHeader, { className: "text-center" }, /* @__PURE__ */ React.createElement(CardTitle, { className: "text-2xl" }, t("Two Factor Verification")), /* @__PURE__ */ React.createElement(CardDescription, null, t(
|
|
55
|
+
"Enter the 6-digit code sent to your email address to continue."
|
|
56
|
+
))), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(
|
|
57
|
+
Input,
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
maxLength: 6,
|
|
61
|
+
value: code,
|
|
62
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "")),
|
|
63
|
+
placeholder: "000000",
|
|
64
|
+
className: "text-center text-2xl tracking-[0.5em] font-mono h-14",
|
|
65
|
+
autoFocus: true
|
|
66
|
+
}
|
|
67
|
+
)), /* @__PURE__ */ React.createElement(
|
|
68
|
+
Button,
|
|
69
|
+
{
|
|
70
|
+
type: "submit",
|
|
71
|
+
className: "w-full h-11",
|
|
72
|
+
disabled: isSubmitting || code.length !== 6
|
|
73
|
+
},
|
|
74
|
+
isSubmitting ? /* @__PURE__ */ React.createElement(Loader, { variant: "dark" }) : t("Verify & Signin")
|
|
75
|
+
), /* @__PURE__ */ React.createElement(
|
|
76
|
+
Button,
|
|
77
|
+
{
|
|
78
|
+
type: "button",
|
|
79
|
+
variant: "ghost",
|
|
80
|
+
className: "w-full",
|
|
81
|
+
onClick: handleResend
|
|
82
|
+
},
|
|
83
|
+
t("Didn't receive a code? Resend")
|
|
84
|
+
)))));
|
|
85
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Logged in successfully": "Logged in successfully",
|
|
3
|
+
"Verification failed": "Verification failed",
|
|
4
|
+
"An error occurred": "An error occurred",
|
|
5
|
+
"New code sent to your email": "New code sent to your email",
|
|
6
|
+
"Failed to send code": "Failed to send code",
|
|
7
|
+
"Two Factor Verification": "Two Factor Verification",
|
|
8
|
+
"Enter the 6-digit code sent to your email address to continue.": "Enter the 6-digit code sent to your email address to continue.",
|
|
9
|
+
"Verify & Signin": "Verify & Signin",
|
|
10
|
+
"Didn't receive a code? Resend": "Didn't receive a code? Resend",
|
|
11
|
+
"2FA enabled": "2FA enabled",
|
|
12
|
+
"2FA disabled": "2FA disabled",
|
|
13
|
+
"Action failed": "Action failed",
|
|
14
|
+
"Two Factor Authentication": "Two Factor Authentication",
|
|
15
|
+
"Add an extra layer of security to your account by requiring a code sent to your email.": "Add an extra layer of security to your account by requiring a code sent to your email.",
|
|
16
|
+
"Email Authentication": "Email Authentication",
|
|
17
|
+
"Currently enabled": "Currently enabled",
|
|
18
|
+
"Currently disabled": "Currently disabled"
|
|
19
|
+
}
|
package/manifest.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arch-cadre/two-factor-email",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Email-based two-factor authentication module for Kryo framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"locales",
|
|
17
|
+
"manifest.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"release": "npm publish --access public --no-git-checks",
|
|
21
|
+
"clean": "rm -rf ./dist",
|
|
22
|
+
"switch:dev": "node scripts/switchToSrc.js",
|
|
23
|
+
"switch:prod": "node scripts/switchToDist.js",
|
|
24
|
+
"dev": "unbuild --stub",
|
|
25
|
+
"build": "unbuild"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@hookform/resolvers": "^3.10.0",
|
|
29
|
+
"@arch-cadre/ui": "^0.0.15",
|
|
30
|
+
"@arch-cadre/modules": "^0.0.15",
|
|
31
|
+
"lucide-react": "^0.475.0",
|
|
32
|
+
"react-hook-form": "^7.54.2",
|
|
33
|
+
"sonner": "^2.0.7",
|
|
34
|
+
"zod": "^3.24.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/pg": "^8.16.0",
|
|
38
|
+
"@arch-cadre/core": "^0.0.15",
|
|
39
|
+
"@types/react": "^19",
|
|
40
|
+
"next": "16.1.1",
|
|
41
|
+
"react": "^19.0.0",
|
|
42
|
+
"typescript": "^5.3.3",
|
|
43
|
+
"unbuild": "^3.6.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@arch-cadre/core": "^0.0.15",
|
|
47
|
+
"@arch-cadre/intl": "^0.0.15",
|
|
48
|
+
"@arch-cadre/ui": "^0.0.15",
|
|
49
|
+
"pg": "^8.16.3",
|
|
50
|
+
"drizzle-orm": "1.0.0-beta.15-859cf75",
|
|
51
|
+
"next": ">=13.0.0",
|
|
52
|
+
"react": "^19.0.0"
|
|
53
|
+
},
|
|
54
|
+
"main": "./dist/index.mjs"
|
|
55
|
+
}
|