mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 08:28:22 +00:00
feat(infisical-pg): completed signup, login, password and backup key migration
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { authDalFactory } from "@app/services/auth/auth-dal";
|
||||
import { authLoginServiceFactory } from "@app/services/auth/auth-login-service";
|
||||
import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service";
|
||||
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
|
||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { tokenDalFactory } from "@app/services/token/token-dal";
|
||||
import { tokenServiceFactory } from "@app/services/token/token-service";
|
||||
|
||||
import { registerV1Routes } from "./v1";
|
||||
import { registerV2Routes } from "./v2";
|
||||
import { registerV3Routes } from "./v3";
|
||||
|
||||
export const registerRoutes = async (
|
||||
server: FastifyZodProvider,
|
||||
{ db, smtp }: { db: Knex; smtp: TSmtpService }
|
||||
) => {
|
||||
// db layers
|
||||
const authDal = authDalFactory(db);
|
||||
const authTokenDal = tokenDalFactory(db);
|
||||
|
||||
// service layers
|
||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
||||
const loginService = authLoginServiceFactory({ authDal, smtpService: smtp, tokenService });
|
||||
const passwordService = authPaswordServiceFactory({ tokenService, smtpService: smtp, authDal });
|
||||
const signupService = authSignupServiceFactory({ tokenService, smtpService: smtp, authDal });
|
||||
|
||||
// inject all services
|
||||
server.decorate("services", {
|
||||
login: loginService,
|
||||
password: passwordService,
|
||||
signup: signupService
|
||||
} as FastifyZodProvider["services"]);
|
||||
|
||||
server.decorate("store", {
|
||||
user: authDal
|
||||
} as FastifyZodProvider["store"]);
|
||||
|
||||
// register routes for v1
|
||||
await server.register(registerV1Routes, { prefix: "/v1" });
|
||||
await server.register(registerV2Routes, { prefix: "/v2" });
|
||||
await server.register(registerV3Routes, { prefix: "/v3" });
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { registerPasswordRouter } from "./password-router";
|
||||
|
||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerPasswordRouter, { prefix: "/password" });
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BackupPrivateKeySchema } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
|
||||
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/srp1",
|
||||
schema: {
|
||||
body: z.object({
|
||||
clientPublicKey: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
serverPublicKey: z.string(),
|
||||
salt: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { salt, serverPublicKey } = await server.services.password.generateServerPubKey(
|
||||
req.auth.userId,
|
||||
req.body.clientPublicKey
|
||||
);
|
||||
return { salt, serverPublicKey };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/change-password",
|
||||
schema: {
|
||||
body: z.object({
|
||||
clientProof: z.string().trim(),
|
||||
protectedKey: z.string().trim(),
|
||||
protectedKeyIV: z.string().trim(),
|
||||
protectedKeyTag: z.string().trim(),
|
||||
encryptedPrivateKey: z.string().trim(),
|
||||
encryptedPrivateKeyIV: z.string().trim(),
|
||||
encryptedPrivateKeyTag: z.string().trim(),
|
||||
salt: z.string().trim(),
|
||||
verifier: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const appCfg = getConfig();
|
||||
await server.services.password.changePassword({ ...req.body, userId: req.auth.userId });
|
||||
|
||||
res.cookie("jid", appCfg.COOKIE_SECRET_SIGN_KEY, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
return { message: "Successfully changed password" };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/backup-private-key",
|
||||
schema: {
|
||||
body: z.object({
|
||||
clientProof: z.string().trim(),
|
||||
encryptedPrivateKey: z.string().trim(),
|
||||
iv: z.string().trim(),
|
||||
tag: z.string().trim(),
|
||||
salt: z.string().trim(),
|
||||
verifier: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
backupPrivateKey: BackupPrivateKeySchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const backupPrivateKey = await server.services.password.createBackupPrivateKey({
|
||||
...req.body,
|
||||
userId: req.auth.userId
|
||||
});
|
||||
if (!backupPrivateKey) throw new Error("Failed to create backup key");
|
||||
|
||||
return { message: "Successfully updated backup private key", backupPrivateKey };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/backup-private-key",
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
backupPrivateKey: BackupPrivateKeySchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const backupPrivateKey = await server.services.password.getBackupPrivateKeyOfUser(
|
||||
req.auth.userId
|
||||
);
|
||||
if (!backupPrivateKey) throw new Error("Failed to find backup key");
|
||||
|
||||
return { message: "Successfully updated backup private key", backupPrivateKey };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { registerMfaRouter } from "./mfa-router";
|
||||
|
||||
export const registerV2Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerMfaRouter, { prefix: "/auth" });
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { AuthTokenType } from "@app/services/auth/auth-signup-type";
|
||||
|
||||
export const registerMfaRouter = async (server: FastifyZodProvider) => {
|
||||
const cfg = getConfig();
|
||||
|
||||
server.addHook("preParsing", async (req, res) => {
|
||||
const authorizationHeader = req.headers.authorization;
|
||||
|
||||
if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) {
|
||||
res.status(401).send({ error: "Missing bearer token" });
|
||||
return;
|
||||
}
|
||||
const token = authorizationHeader.split(" ")[1];
|
||||
if (!token) res.status(401).send({ error: "Missing bearer token" });
|
||||
|
||||
const decodedToken = jwt.verify(token, cfg.JWT_AUTH_SECRET) as JwtPayload;
|
||||
if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN)
|
||||
throw new Error("Unauthorized access");
|
||||
|
||||
const user = await server.store.user.getUserById(decodedToken.userId);
|
||||
if (!user) throw new Error("User not found");
|
||||
req.mfa.userId = user.id;
|
||||
req.mfa.user = user;
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/mfa/send",
|
||||
method: "POST",
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
await server.services.login.resendMfaToken(req.mfa.userId);
|
||||
return { message: "Successfully send new mfa code" };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/mfa/verify",
|
||||
method: "POST",
|
||||
schema: {
|
||||
body: z.object({
|
||||
mfaToken: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
encryptionVersion: z.number().default(1).optional(),
|
||||
protectedKey: z.string(),
|
||||
protectedKeyIV: z.string(),
|
||||
protectedKeyTag: z.string(),
|
||||
publicKey: z.string(),
|
||||
encryptedPrivateKey: z.string(),
|
||||
iv: z.string(),
|
||||
tag: z.string(),
|
||||
token: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const userAgent = req.headers["user-agent"];
|
||||
if (!userAgent) throw new Error("user agent header is required");
|
||||
const appCfg = getConfig();
|
||||
|
||||
const { user, token } = await server.services.login.verifyMfaToken({
|
||||
userAgent,
|
||||
ip: req.realIp,
|
||||
userId: req.mfa.userId,
|
||||
mfaToken: req.body.mfaToken
|
||||
});
|
||||
|
||||
res.setCookie("jid", token.refresh, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
return { token: token.access, ...user };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { registerLoginRouter } from "./login-router";
|
||||
import { registerSignupRouter } from "./signup-router";
|
||||
|
||||
export const registerV3Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerSignupRouter, { prefix: "/signup" });
|
||||
await server.register(registerLoginRouter, { prefix: "/auth" });
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
|
||||
export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/login1",
|
||||
schema: {
|
||||
body: z.object({
|
||||
email: z.string().email().trim(),
|
||||
providerAuthToken: z.string().trim().optional(),
|
||||
clientPublicKey: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
serverPublicKey: z.string(),
|
||||
salt: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { serverPublicKey, salt } = await server.services.login.loginGenServerPublicKey({
|
||||
email: req.body.email,
|
||||
clientPublicKey: req.body.clientPublicKey,
|
||||
providerAuthToken: req.body.providerAuthToken
|
||||
});
|
||||
|
||||
return { serverPublicKey, salt };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/login2",
|
||||
schema: {
|
||||
body: z.object({
|
||||
email: z.string().email().trim(),
|
||||
providerAuthToken: z.string().trim().optional(),
|
||||
clientProof: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.discriminatedUnion("mfaEnabled", [
|
||||
z.object({ mfaEnabled: z.literal(true), token: z.string() }),
|
||||
z.object({
|
||||
mfaEnabled: z.literal(false),
|
||||
encryptionVersion: z.number().default(1).optional(),
|
||||
protectedKey: z.string(),
|
||||
protectedKeyIV: z.string(),
|
||||
protectedKeyTag: z.string(),
|
||||
publicKey: z.string(),
|
||||
encryptedPrivateKey: z.string(),
|
||||
iv: z.string(),
|
||||
tag: z.string(),
|
||||
token: z.string()
|
||||
})
|
||||
])
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const userAgent = req.headers["user-agent"];
|
||||
if (!userAgent) throw new Error("user agent header is required");
|
||||
const appCfg = getConfig();
|
||||
|
||||
const data = await server.services.login.loginExchangeClientProof({
|
||||
email: req.body.email,
|
||||
ip: req.realIp,
|
||||
userAgent,
|
||||
clientProof: req.body.clientProof
|
||||
});
|
||||
|
||||
if (data.isMfaEnabled) {
|
||||
return { mfaEnabled: true, token: data.token } as const; // for discriminated union
|
||||
}
|
||||
|
||||
res.setCookie("jid", data.token.refresh, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
return {
|
||||
mfaEnabled: false,
|
||||
encryptionVersion: data.user.encryptionVersion,
|
||||
token: data.token.access,
|
||||
publicKey: data.user.publicKey,
|
||||
encryptedPrivateKey: data.user.encryptedPrivateKey,
|
||||
iv: data.user.iv,
|
||||
tag: data.user.tag,
|
||||
protectedKey: data.user.protectedKey,
|
||||
protectedKeyIV: data.user.protectedKeyIV,
|
||||
protectedKeyTag: data.user.protectedKeyTag
|
||||
} as const;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { UserSchema } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
|
||||
export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/email/signup",
|
||||
method: "POST",
|
||||
schema: {
|
||||
body: z.object({
|
||||
email: z.string().email().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
await server.services.signup.beginEmailSignupProcess(req.body.email);
|
||||
return { message: `Sent an email verification code to ${req.body.email}` };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/email/verify",
|
||||
method: "POST",
|
||||
schema: {
|
||||
body: z.object({
|
||||
email: z.string().email().trim(),
|
||||
code: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
token: z.string(),
|
||||
user: UserSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { token, user } = await server.services.signup.verifyEmailSignup(
|
||||
req.body.email,
|
||||
req.body.code
|
||||
);
|
||||
return { message: "Successfuly verified email", token, user };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/complete-account/signup",
|
||||
method: "POST",
|
||||
schema: {
|
||||
body: z.object({
|
||||
email: z.string().email().trim(),
|
||||
firstName: z.string().trim(),
|
||||
lastName: z.string().trim().optional(),
|
||||
protectedKey: z.string().trim(),
|
||||
protectedKeyIV: z.string().trim(),
|
||||
protectedKeyTag: z.string().trim(),
|
||||
publicKey: z.string().trim(),
|
||||
encryptedPrivateKey: z.string().trim(),
|
||||
encryptedPrivateKeyIV: z.string().trim(),
|
||||
encryptedPrivateKeyTag: z.string().trim(),
|
||||
salt: z.string().trim(),
|
||||
verifier: z.string().trim(),
|
||||
organizationName: z.string().trim(),
|
||||
providerAuthToken: z.string().trim().optional().nullish(),
|
||||
attributionSource: z.string().trim().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
user: UserSchema,
|
||||
token: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const userAgent = req.headers["user-agent"];
|
||||
if (!userAgent) throw new Error("user agent header is required");
|
||||
const appCfg = getConfig();
|
||||
|
||||
const { user, accessToken, refreshToken } =
|
||||
await server.services.signup.completeEmailAccountSignup({
|
||||
...req.body,
|
||||
ip: req.realIp,
|
||||
userAgent
|
||||
});
|
||||
|
||||
res.setCookie("jid", refreshToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
// TODO(akhilmhdh-pg): add telemetry service
|
||||
|
||||
return { message: "Successfully set up account", user, token: accessToken };
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user