@peterbud/nuxt-aegis 1.1.0-alpha
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/README.md +166 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +354 -0
- package/dist/runtime/app/composables/useAuth.d.ts +85 -0
- package/dist/runtime/app/composables/useAuth.js +187 -0
- package/dist/runtime/app/middleware/auth-logged-in.d.ts +16 -0
- package/dist/runtime/app/middleware/auth-logged-in.js +25 -0
- package/dist/runtime/app/middleware/auth-logged-out.d.ts +20 -0
- package/dist/runtime/app/middleware/auth-logged-out.js +17 -0
- package/dist/runtime/app/pages/AuthCallback.d.vue.ts +3 -0
- package/dist/runtime/app/pages/AuthCallback.vue +92 -0
- package/dist/runtime/app/pages/AuthCallback.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/api.client.d.ts +11 -0
- package/dist/runtime/app/plugins/api.client.js +92 -0
- package/dist/runtime/app/plugins/api.server.d.ts +13 -0
- package/dist/runtime/app/plugins/api.server.js +28 -0
- package/dist/runtime/app/plugins/ssr-state.server.d.ts +2 -0
- package/dist/runtime/app/plugins/ssr-state.server.js +13 -0
- package/dist/runtime/app/router.options.d.ts +12 -0
- package/dist/runtime/app/router.options.js +11 -0
- package/dist/runtime/app/utils/logger.d.ts +18 -0
- package/dist/runtime/app/utils/logger.js +48 -0
- package/dist/runtime/app/utils/redirectValidation.d.ts +18 -0
- package/dist/runtime/app/utils/redirectValidation.js +21 -0
- package/dist/runtime/app/utils/routeMatching.d.ts +13 -0
- package/dist/runtime/app/utils/routeMatching.js +10 -0
- package/dist/runtime/app/utils/tokenStore.d.ts +24 -0
- package/dist/runtime/app/utils/tokenStore.js +14 -0
- package/dist/runtime/app/utils/tokenUtils.d.ts +17 -0
- package/dist/runtime/app/utils/tokenUtils.js +4 -0
- package/dist/runtime/server/middleware/auth.d.ts +6 -0
- package/dist/runtime/server/middleware/auth.js +82 -0
- package/dist/runtime/server/plugins/ssr-auth.d.ts +7 -0
- package/dist/runtime/server/plugins/ssr-auth.js +82 -0
- package/dist/runtime/server/providers/auth0.d.ts +12 -0
- package/dist/runtime/server/providers/auth0.js +57 -0
- package/dist/runtime/server/providers/github.d.ts +12 -0
- package/dist/runtime/server/providers/github.js +44 -0
- package/dist/runtime/server/providers/google.d.ts +12 -0
- package/dist/runtime/server/providers/google.js +46 -0
- package/dist/runtime/server/providers/mock.d.ts +37 -0
- package/dist/runtime/server/providers/mock.js +129 -0
- package/dist/runtime/server/providers/oauthBase.d.ts +72 -0
- package/dist/runtime/server/providers/oauthBase.js +183 -0
- package/dist/runtime/server/routes/impersonate.post.d.ts +21 -0
- package/dist/runtime/server/routes/impersonate.post.js +68 -0
- package/dist/runtime/server/routes/logout.post.d.ts +9 -0
- package/dist/runtime/server/routes/logout.post.js +24 -0
- package/dist/runtime/server/routes/me.get.d.ts +6 -0
- package/dist/runtime/server/routes/me.get.js +11 -0
- package/dist/runtime/server/routes/mock/authorize.get.d.ts +29 -0
- package/dist/runtime/server/routes/mock/authorize.get.js +103 -0
- package/dist/runtime/server/routes/mock/token.post.d.ts +31 -0
- package/dist/runtime/server/routes/mock/token.post.js +88 -0
- package/dist/runtime/server/routes/mock/userinfo.get.d.ts +27 -0
- package/dist/runtime/server/routes/mock/userinfo.get.js +59 -0
- package/dist/runtime/server/routes/password/change.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/change.post.js +108 -0
- package/dist/runtime/server/routes/password/login-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/login-verify.get.js +79 -0
- package/dist/runtime/server/routes/password/login.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/login.post.js +66 -0
- package/dist/runtime/server/routes/password/register-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/register-verify.get.js +86 -0
- package/dist/runtime/server/routes/password/register.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/register.post.js +87 -0
- package/dist/runtime/server/routes/password/reset-complete.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/reset-complete.post.js +75 -0
- package/dist/runtime/server/routes/password/reset-request.post.d.ts +5 -0
- package/dist/runtime/server/routes/password/reset-request.post.js +52 -0
- package/dist/runtime/server/routes/password/reset-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/reset-verify.get.js +50 -0
- package/dist/runtime/server/routes/refresh.post.d.ts +8 -0
- package/dist/runtime/server/routes/refresh.post.js +102 -0
- package/dist/runtime/server/routes/token.post.d.ts +28 -0
- package/dist/runtime/server/routes/token.post.js +90 -0
- package/dist/runtime/server/routes/unimpersonate.post.d.ts +16 -0
- package/dist/runtime/server/routes/unimpersonate.post.js +65 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/auth.d.ts +94 -0
- package/dist/runtime/server/utils/auth.js +54 -0
- package/dist/runtime/server/utils/authCodeStore.d.ts +137 -0
- package/dist/runtime/server/utils/authCodeStore.js +123 -0
- package/dist/runtime/server/utils/cookies.d.ts +15 -0
- package/dist/runtime/server/utils/cookies.js +23 -0
- package/dist/runtime/server/utils/customClaims.d.ts +37 -0
- package/dist/runtime/server/utils/customClaims.js +45 -0
- package/dist/runtime/server/utils/handler.d.ts +77 -0
- package/dist/runtime/server/utils/handler.js +7 -0
- package/dist/runtime/server/utils/impersonation.d.ts +48 -0
- package/dist/runtime/server/utils/impersonation.js +259 -0
- package/dist/runtime/server/utils/jwt.d.ts +24 -0
- package/dist/runtime/server/utils/jwt.js +77 -0
- package/dist/runtime/server/utils/logger.d.ts +18 -0
- package/dist/runtime/server/utils/logger.js +49 -0
- package/dist/runtime/server/utils/magicCodeStore.d.ts +27 -0
- package/dist/runtime/server/utils/magicCodeStore.js +66 -0
- package/dist/runtime/server/utils/mockCodeStore.d.ts +89 -0
- package/dist/runtime/server/utils/mockCodeStore.js +71 -0
- package/dist/runtime/server/utils/password.d.ts +33 -0
- package/dist/runtime/server/utils/password.js +48 -0
- package/dist/runtime/server/utils/refreshToken.d.ts +74 -0
- package/dist/runtime/server/utils/refreshToken.js +108 -0
- package/dist/runtime/server/utils/resetSessionStore.d.ts +12 -0
- package/dist/runtime/server/utils/resetSessionStore.js +29 -0
- package/dist/runtime/tasks/cleanup/magic-codes.d.ts +10 -0
- package/dist/runtime/tasks/cleanup/magic-codes.js +79 -0
- package/dist/runtime/tasks/cleanup/refresh-tokens.d.ts +10 -0
- package/dist/runtime/tasks/cleanup/refresh-tokens.js +55 -0
- package/dist/runtime/tasks/cleanup/reset-sessions.d.ts +8 -0
- package/dist/runtime/tasks/cleanup/reset-sessions.js +45 -0
- package/dist/runtime/types/augmentation.d.ts +73 -0
- package/dist/runtime/types/augmentation.js +0 -0
- package/dist/runtime/types/authCode.d.ts +60 -0
- package/dist/runtime/types/authCode.js +0 -0
- package/dist/runtime/types/callbacks.d.ts +54 -0
- package/dist/runtime/types/callbacks.js +0 -0
- package/dist/runtime/types/config.d.ts +129 -0
- package/dist/runtime/types/config.js +0 -0
- package/dist/runtime/types/hooks.d.ts +118 -0
- package/dist/runtime/types/hooks.js +0 -0
- package/dist/runtime/types/index.d.ts +13 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/providers.d.ts +212 -0
- package/dist/runtime/types/providers.js +0 -0
- package/dist/runtime/types/refresh.d.ts +61 -0
- package/dist/runtime/types/refresh.js +0 -0
- package/dist/runtime/types/routes.d.ts +30 -0
- package/dist/runtime/types/routes.js +0 -0
- package/dist/runtime/types/token.d.ts +182 -0
- package/dist/runtime/types/token.js +0 -0
- package/dist/types.d.mts +7 -0
- package/package.json +80 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { getUserForMockToken } from "../../utils/mockCodeStore.js";
|
|
4
|
+
import { createLogger } from "../../utils/logger.js";
|
|
5
|
+
const logger = createLogger("MockUserInfo");
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
8
|
+
const mockConfig = runtimeConfig.nuxtAegis?.providers?.mock;
|
|
9
|
+
if (!mockConfig) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 500,
|
|
12
|
+
statusMessage: "Internal Server Error",
|
|
13
|
+
message: "[nuxt-aegis] Mock provider not configured"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const authHeader = getHeader(event, "authorization");
|
|
17
|
+
if (!authHeader) {
|
|
18
|
+
throw createError({
|
|
19
|
+
statusCode: 401,
|
|
20
|
+
statusMessage: "Unauthorized",
|
|
21
|
+
message: "Missing authorization header"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (!authHeader.startsWith("Bearer ")) {
|
|
25
|
+
throw createError({
|
|
26
|
+
statusCode: 401,
|
|
27
|
+
statusMessage: "Unauthorized",
|
|
28
|
+
message: "Invalid authorization header format"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const accessToken = authHeader.substring(7);
|
|
32
|
+
if (!accessToken.startsWith("mock_aegis_access_")) {
|
|
33
|
+
throw createError({
|
|
34
|
+
statusCode: 401,
|
|
35
|
+
statusMessage: "Unauthorized",
|
|
36
|
+
message: "Invalid access token"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const userId = getUserForMockToken(accessToken);
|
|
40
|
+
if (!userId) {
|
|
41
|
+
throw createError({
|
|
42
|
+
statusCode: 401,
|
|
43
|
+
statusMessage: "Unauthorized",
|
|
44
|
+
message: "Invalid or expired access token"
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const userData = mockConfig.mockUsers[userId];
|
|
48
|
+
if (!userData) {
|
|
49
|
+
throw createError({
|
|
50
|
+
statusCode: 500,
|
|
51
|
+
statusMessage: "Internal Server Error",
|
|
52
|
+
message: `[nuxt-aegis] Mock user '${userId}' not found in configuration`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
logger.debug("UserInfo request returning user:", userId);
|
|
56
|
+
return {
|
|
57
|
+
...userData
|
|
58
|
+
};
|
|
59
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError, getHeader, getCookie } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
4
|
+
import { verifyToken } from "../../utils/jwt.js";
|
|
5
|
+
import { verifyPassword, validatePasswordStrength, hashPassword, normalizeEmail } from "../../utils/password.js";
|
|
6
|
+
import { deleteUserRefreshTokens, hashRefreshToken } from "../../utils/refreshToken.js";
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const config = useRuntimeConfig();
|
|
9
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
10
|
+
if (!passwordConfig) {
|
|
11
|
+
throw createError({
|
|
12
|
+
statusCode: 404,
|
|
13
|
+
message: "Password provider is not configured"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const handler = useAegisHandler();
|
|
17
|
+
if (!handler?.password) {
|
|
18
|
+
throw createError({
|
|
19
|
+
statusCode: 500,
|
|
20
|
+
message: "Password handler is not implemented"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const authHeader = getHeader(event, "authorization");
|
|
24
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
25
|
+
throw createError({
|
|
26
|
+
statusCode: 401,
|
|
27
|
+
message: "Authentication required"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const token = authHeader.substring(7);
|
|
31
|
+
const tokenConfig = config.nuxtAegis?.token;
|
|
32
|
+
if (!tokenConfig?.secret) {
|
|
33
|
+
throw createError({
|
|
34
|
+
statusCode: 500,
|
|
35
|
+
message: "Token secret is not configured"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const payload = await verifyToken(token, tokenConfig.secret).catch(() => {
|
|
39
|
+
throw createError({
|
|
40
|
+
statusCode: 401,
|
|
41
|
+
message: "Invalid token"
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
if (!payload) {
|
|
45
|
+
throw createError({
|
|
46
|
+
statusCode: 401,
|
|
47
|
+
message: "Invalid token payload"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const email = payload.email;
|
|
51
|
+
if (!email) {
|
|
52
|
+
throw createError({
|
|
53
|
+
statusCode: 401,
|
|
54
|
+
message: "Invalid token payload"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const body = await readBody(event);
|
|
58
|
+
const { currentPassword, newPassword } = body;
|
|
59
|
+
if (!currentPassword || !newPassword) {
|
|
60
|
+
throw createError({
|
|
61
|
+
statusCode: 400,
|
|
62
|
+
message: "Current and new password are required"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const normalizedEmail = normalizeEmail(email);
|
|
66
|
+
const user = await handler.password.findUser(normalizedEmail);
|
|
67
|
+
if (!user) {
|
|
68
|
+
throw createError({
|
|
69
|
+
statusCode: 404,
|
|
70
|
+
message: "User not found"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const isValidPassword = handler.password.verifyPassword ? await handler.password.verifyPassword(currentPassword, user.hashedPassword) : await verifyPassword(currentPassword, user.hashedPassword);
|
|
74
|
+
if (!isValidPassword) {
|
|
75
|
+
throw createError({
|
|
76
|
+
statusCode: 401,
|
|
77
|
+
message: "Current password is incorrect"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const passwordPolicy = passwordConfig.passwordPolicy || {};
|
|
81
|
+
const passwordValidation = handler.password.validatePassword ? await handler.password.validatePassword(newPassword) : validatePasswordStrength(newPassword, passwordPolicy);
|
|
82
|
+
if (passwordValidation !== true) {
|
|
83
|
+
let errors = ["Invalid password"];
|
|
84
|
+
if (Array.isArray(passwordValidation)) {
|
|
85
|
+
errors = passwordValidation;
|
|
86
|
+
} else if (typeof passwordValidation === "object" && passwordValidation !== null && "errors" in passwordValidation) {
|
|
87
|
+
errors = passwordValidation.errors;
|
|
88
|
+
}
|
|
89
|
+
throw createError({
|
|
90
|
+
statusCode: 400,
|
|
91
|
+
message: "New password does not meet requirements",
|
|
92
|
+
data: { errors }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const newHashedPassword = handler.password.hashPassword ? await handler.password.hashPassword(newPassword) : await hashPassword(newPassword);
|
|
96
|
+
await handler.password.upsertUser({
|
|
97
|
+
...user,
|
|
98
|
+
hashedPassword: newHashedPassword
|
|
99
|
+
});
|
|
100
|
+
const refreshCookieName = config.nuxtAegis?.tokenRefresh?.cookie?.cookieName || "nuxt-aegis-refresh";
|
|
101
|
+
const currentRefreshToken = getCookie(event, refreshCookieName);
|
|
102
|
+
let currentTokenHash;
|
|
103
|
+
if (currentRefreshToken) {
|
|
104
|
+
currentTokenHash = hashRefreshToken(currentRefreshToken);
|
|
105
|
+
}
|
|
106
|
+
await deleteUserRefreshTokens(normalizedEmail, currentTokenHash, event);
|
|
107
|
+
return { success: true };
|
|
108
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery, createError, sendRedirect } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import { validateAndIncrementAttempts, retrieveAndDeleteMagicCode } from "../../utils/magicCodeStore.js";
|
|
6
|
+
import { storeAuthCode, generateAuthCode } from "../../utils/authCodeStore.js";
|
|
7
|
+
const logger = createLogger("PasswordLoginVerify");
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const config = useRuntimeConfig();
|
|
10
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
11
|
+
if (!passwordConfig) {
|
|
12
|
+
throw createError({
|
|
13
|
+
statusCode: 404,
|
|
14
|
+
message: "Password provider is not configured"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const handler = useAegisHandler();
|
|
18
|
+
if (!handler?.password) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 500,
|
|
21
|
+
message: "Password handler is not implemented"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const query = getQuery(event);
|
|
25
|
+
const code = query.code;
|
|
26
|
+
if (!code) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
message: "Verification code is required"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const magicCodeData = await validateAndIncrementAttempts(code);
|
|
33
|
+
if (!magicCodeData) {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 400,
|
|
36
|
+
message: "Invalid or expired code"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (magicCodeData.type !== "login") {
|
|
40
|
+
throw createError({
|
|
41
|
+
statusCode: 400,
|
|
42
|
+
message: "Invalid code type"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const { email } = magicCodeData;
|
|
46
|
+
try {
|
|
47
|
+
const user = await handler.password.findUser(email);
|
|
48
|
+
if (!user) {
|
|
49
|
+
throw createError({
|
|
50
|
+
statusCode: 404,
|
|
51
|
+
message: "User not found"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
await retrieveAndDeleteMagicCode(code);
|
|
55
|
+
const providerUserInfo = {
|
|
56
|
+
sub: user.id || user.email,
|
|
57
|
+
provider: "password",
|
|
58
|
+
...user
|
|
59
|
+
};
|
|
60
|
+
const authCode = generateAuthCode();
|
|
61
|
+
await storeAuthCode(
|
|
62
|
+
authCode,
|
|
63
|
+
providerUserInfo,
|
|
64
|
+
{ access_token: "password_auth" },
|
|
65
|
+
"password",
|
|
66
|
+
void 0,
|
|
67
|
+
60,
|
|
68
|
+
event
|
|
69
|
+
);
|
|
70
|
+
const redirectUrl = `/auth/callback?code=${authCode}`;
|
|
71
|
+
return await sendRedirect(event, redirectUrl, 302);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.error("Login verification failed", error);
|
|
74
|
+
throw createError({
|
|
75
|
+
statusCode: 500,
|
|
76
|
+
message: "Login failed"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeEmail,
|
|
7
|
+
verifyPassword,
|
|
8
|
+
obfuscateMagicCode
|
|
9
|
+
} from "../../utils/password.js";
|
|
10
|
+
import { storeMagicCode } from "../../utils/magicCodeStore.js";
|
|
11
|
+
const logger = createLogger("PasswordLogin");
|
|
12
|
+
export default defineEventHandler(async (event) => {
|
|
13
|
+
const config = useRuntimeConfig();
|
|
14
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
15
|
+
if (!passwordConfig) {
|
|
16
|
+
throw createError({
|
|
17
|
+
statusCode: 404,
|
|
18
|
+
message: "Password provider is not configured"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const handler = useAegisHandler();
|
|
22
|
+
if (!handler?.password) {
|
|
23
|
+
throw createError({
|
|
24
|
+
statusCode: 500,
|
|
25
|
+
message: "Password handler is not implemented"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const body = await readBody(event);
|
|
29
|
+
const { email, password } = body;
|
|
30
|
+
if (!email || !password) {
|
|
31
|
+
throw createError({
|
|
32
|
+
statusCode: 400,
|
|
33
|
+
message: "Email and password are required"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const normalizedEmail = normalizeEmail(email);
|
|
37
|
+
const user = await handler.password.findUser(normalizedEmail);
|
|
38
|
+
let isValidPassword = false;
|
|
39
|
+
if (user) {
|
|
40
|
+
isValidPassword = handler.password.verifyPassword ? await handler.password.verifyPassword(password, user.hashedPassword) : await verifyPassword(password, user.hashedPassword);
|
|
41
|
+
}
|
|
42
|
+
if (!user || !isValidPassword) {
|
|
43
|
+
throw createError({
|
|
44
|
+
statusCode: 401,
|
|
45
|
+
message: "Invalid email or password"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const code = await storeMagicCode(
|
|
49
|
+
normalizedEmail,
|
|
50
|
+
"login",
|
|
51
|
+
{},
|
|
52
|
+
passwordConfig.magicCodeTTL,
|
|
53
|
+
passwordConfig.magicCodeMaxAttempts
|
|
54
|
+
);
|
|
55
|
+
logger.debug(`Magic code generated for ${normalizedEmail}: ${obfuscateMagicCode(code)}`);
|
|
56
|
+
try {
|
|
57
|
+
await handler.password.sendVerificationCode(normalizedEmail, code, "login");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error("Failed to send verification code", error);
|
|
60
|
+
throw createError({
|
|
61
|
+
statusCode: 500,
|
|
62
|
+
message: "Failed to send verification code"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return { success: true };
|
|
66
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery, createError, sendRedirect } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import { validateAndIncrementAttempts, retrieveAndDeleteMagicCode } from "../../utils/magicCodeStore.js";
|
|
6
|
+
import { storeAuthCode, generateAuthCode } from "../../utils/authCodeStore.js";
|
|
7
|
+
const logger = createLogger("PasswordRegisterVerify");
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const config = useRuntimeConfig();
|
|
10
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
11
|
+
if (!passwordConfig) {
|
|
12
|
+
throw createError({
|
|
13
|
+
statusCode: 404,
|
|
14
|
+
message: "Password provider is not configured"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const handler = useAegisHandler();
|
|
18
|
+
if (!handler?.password) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 500,
|
|
21
|
+
message: "Password handler is not implemented"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const query = getQuery(event);
|
|
25
|
+
const code = query.code;
|
|
26
|
+
if (!code) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
message: "Verification code is required"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const magicCodeData = await validateAndIncrementAttempts(code);
|
|
33
|
+
if (!magicCodeData) {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 400,
|
|
36
|
+
message: "Invalid or expired code"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (magicCodeData.type !== "register") {
|
|
40
|
+
throw createError({
|
|
41
|
+
statusCode: 400,
|
|
42
|
+
message: "Invalid code type"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const { email, hashedPassword } = magicCodeData;
|
|
46
|
+
if (!hashedPassword) {
|
|
47
|
+
throw createError({
|
|
48
|
+
statusCode: 500,
|
|
49
|
+
message: "Invalid code data"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await handler.password.upsertUser({
|
|
54
|
+
email,
|
|
55
|
+
hashedPassword
|
|
56
|
+
});
|
|
57
|
+
const user = await handler.password.findUser(email);
|
|
58
|
+
if (!user) {
|
|
59
|
+
throw new Error("User not found after creation");
|
|
60
|
+
}
|
|
61
|
+
await retrieveAndDeleteMagicCode(code);
|
|
62
|
+
const providerUserInfo = {
|
|
63
|
+
sub: user.id || user.email,
|
|
64
|
+
provider: "password",
|
|
65
|
+
...user
|
|
66
|
+
};
|
|
67
|
+
const authCode = generateAuthCode();
|
|
68
|
+
await storeAuthCode(
|
|
69
|
+
authCode,
|
|
70
|
+
providerUserInfo,
|
|
71
|
+
{ access_token: "password_auth" },
|
|
72
|
+
"password",
|
|
73
|
+
void 0,
|
|
74
|
+
60,
|
|
75
|
+
event
|
|
76
|
+
);
|
|
77
|
+
const redirectUrl = `/auth/callback?code=${authCode}`;
|
|
78
|
+
return await sendRedirect(event, redirectUrl, 302);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error("Registration verification failed", error);
|
|
81
|
+
throw createError({
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
message: "Registration failed"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeEmail,
|
|
7
|
+
validateEmailFormat,
|
|
8
|
+
validatePasswordStrength,
|
|
9
|
+
hashPassword,
|
|
10
|
+
obfuscateMagicCode
|
|
11
|
+
} from "../../utils/password.js";
|
|
12
|
+
import { storeMagicCode } from "../../utils/magicCodeStore.js";
|
|
13
|
+
const logger = createLogger("PasswordRegister");
|
|
14
|
+
export default defineEventHandler(async (event) => {
|
|
15
|
+
const config = useRuntimeConfig();
|
|
16
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
17
|
+
logger.debug("Register endpoint called");
|
|
18
|
+
if (!passwordConfig) {
|
|
19
|
+
logger.error("Password provider is not configured");
|
|
20
|
+
throw createError({
|
|
21
|
+
statusCode: 404,
|
|
22
|
+
message: "Password provider is not configured"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const handler = useAegisHandler();
|
|
26
|
+
if (!handler?.password) {
|
|
27
|
+
logger.error("Password handler is not implemented");
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 500,
|
|
30
|
+
message: "Password handler is not implemented"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const body = await readBody(event);
|
|
34
|
+
logger.debug("Request body:", body);
|
|
35
|
+
const { email, password } = body;
|
|
36
|
+
if (!email || !password) {
|
|
37
|
+
logger.error("Email or password missing", { email: !!email, password: !!password });
|
|
38
|
+
throw createError({
|
|
39
|
+
statusCode: 400,
|
|
40
|
+
message: "Email and password are required"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const normalizedEmail = normalizeEmail(email);
|
|
44
|
+
const emailValidation = validateEmailFormat(normalizedEmail);
|
|
45
|
+
if (!emailValidation.valid) {
|
|
46
|
+
throw createError({
|
|
47
|
+
statusCode: 400,
|
|
48
|
+
message: emailValidation.error
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const passwordPolicy = passwordConfig.passwordPolicy || {};
|
|
52
|
+
const passwordValidation = handler.password.validatePassword ? await handler.password.validatePassword(password) : validatePasswordStrength(password, passwordPolicy);
|
|
53
|
+
if (passwordValidation !== true) {
|
|
54
|
+
const errors = Array.isArray(passwordValidation) ? passwordValidation : ["Invalid password"];
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: 400,
|
|
57
|
+
message: "Password does not meet requirements",
|
|
58
|
+
data: { errors }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const existingUser = await handler.password.findUser(normalizedEmail);
|
|
62
|
+
if (existingUser) {
|
|
63
|
+
throw createError({
|
|
64
|
+
statusCode: 409,
|
|
65
|
+
message: "User already exists"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const hashedPassword = handler.password.hashPassword ? await handler.password.hashPassword(password) : await hashPassword(password);
|
|
69
|
+
const code = await storeMagicCode(
|
|
70
|
+
normalizedEmail,
|
|
71
|
+
"register",
|
|
72
|
+
{ hashedPassword },
|
|
73
|
+
passwordConfig.magicCodeTTL,
|
|
74
|
+
passwordConfig.magicCodeMaxAttempts
|
|
75
|
+
);
|
|
76
|
+
logger.debug(`Magic code generated for ${normalizedEmail}: ${obfuscateMagicCode(code)}`);
|
|
77
|
+
try {
|
|
78
|
+
await handler.password.sendVerificationCode(normalizedEmail, code, "register");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error("Failed to send verification code", error);
|
|
81
|
+
throw createError({
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
message: "Failed to send verification code"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return { success: true };
|
|
87
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import { validateAndDeleteResetSession } from "../../utils/resetSessionStore.js";
|
|
6
|
+
import { validatePasswordStrength, hashPassword } from "../../utils/password.js";
|
|
7
|
+
const logger = createLogger("PasswordResetComplete");
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const config = useRuntimeConfig();
|
|
10
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
11
|
+
if (!passwordConfig) {
|
|
12
|
+
throw createError({
|
|
13
|
+
statusCode: 404,
|
|
14
|
+
message: "Password provider is not configured"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const handler = useAegisHandler();
|
|
18
|
+
if (!handler?.password) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 500,
|
|
21
|
+
message: "Password handler is not implemented"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const body = await readBody(event);
|
|
25
|
+
const { sessionId, password } = body;
|
|
26
|
+
if (!sessionId || !password) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
message: "Session ID and password are required"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const email = await validateAndDeleteResetSession(sessionId);
|
|
33
|
+
if (!email) {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 400,
|
|
36
|
+
message: "Invalid or expired reset session"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const passwordPolicy = passwordConfig.passwordPolicy || {};
|
|
40
|
+
const passwordValidation = handler.password.validatePassword ? await handler.password.validatePassword(password) : validatePasswordStrength(password, passwordPolicy);
|
|
41
|
+
if (passwordValidation !== true) {
|
|
42
|
+
let errors = ["Invalid password"];
|
|
43
|
+
if (Array.isArray(passwordValidation)) {
|
|
44
|
+
errors = passwordValidation;
|
|
45
|
+
} else if (typeof passwordValidation === "object" && passwordValidation !== null && "errors" in passwordValidation) {
|
|
46
|
+
errors = passwordValidation.errors;
|
|
47
|
+
}
|
|
48
|
+
throw createError({
|
|
49
|
+
statusCode: 400,
|
|
50
|
+
message: "Password does not meet requirements",
|
|
51
|
+
data: { errors }
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const hashedPassword = handler.password.hashPassword ? await handler.password.hashPassword(password) : await hashPassword(password);
|
|
56
|
+
const user = await handler.password.findUser(email);
|
|
57
|
+
if (!user) {
|
|
58
|
+
throw createError({
|
|
59
|
+
statusCode: 404,
|
|
60
|
+
message: "User not found"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
await handler.password.upsertUser({
|
|
64
|
+
...user,
|
|
65
|
+
hashedPassword
|
|
66
|
+
});
|
|
67
|
+
return { success: true };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error("Password reset failed", error);
|
|
70
|
+
throw createError({
|
|
71
|
+
statusCode: 500,
|
|
72
|
+
message: "Password reset failed"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createLogger } from "../../utils/logger.js";
|
|
4
|
+
import { useAegisHandler } from "../../utils/handler.js";
|
|
5
|
+
import { normalizeEmail, obfuscateMagicCode } from "../../utils/password.js";
|
|
6
|
+
import { storeMagicCode } from "../../utils/magicCodeStore.js";
|
|
7
|
+
const logger = createLogger("PasswordResetRequest");
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const config = useRuntimeConfig();
|
|
10
|
+
const passwordConfig = config.nuxtAegis?.providers?.password;
|
|
11
|
+
if (!passwordConfig) {
|
|
12
|
+
throw createError({
|
|
13
|
+
statusCode: 404,
|
|
14
|
+
message: "Password provider is not configured"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const handler = useAegisHandler();
|
|
18
|
+
if (!handler?.password) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 500,
|
|
21
|
+
message: "Password handler is not implemented"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const body = await readBody(event);
|
|
25
|
+
const { email } = body;
|
|
26
|
+
if (!email) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
message: "Email is required"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const normalizedEmail = normalizeEmail(email);
|
|
33
|
+
const user = await handler.password.findUser(normalizedEmail);
|
|
34
|
+
if (user) {
|
|
35
|
+
const code = await storeMagicCode(
|
|
36
|
+
normalizedEmail,
|
|
37
|
+
"reset",
|
|
38
|
+
{},
|
|
39
|
+
passwordConfig.magicCodeTTL,
|
|
40
|
+
passwordConfig.magicCodeMaxAttempts
|
|
41
|
+
);
|
|
42
|
+
logger.debug(`Magic code generated for ${normalizedEmail}: ${obfuscateMagicCode(code)}`);
|
|
43
|
+
try {
|
|
44
|
+
await handler.password.sendVerificationCode(normalizedEmail, code, "reset");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.error("Failed to send verification code", error);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
logger.debug(`Password reset requested for non-existent user: ${normalizedEmail}`);
|
|
50
|
+
}
|
|
51
|
+
return { success: true, message: "If an account exists with this email, a verification code has been sent." };
|
|
52
|
+
});
|