mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 00:27:30 +00:00
feat: added login test and fixed super admin bug
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
import { testUser } from "@app/db/seeds/1-user";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
|
describe("Login V1 Router", async () => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const client = new jsrp.client();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
client.init({ username: testUser.email, password: testUser.password }, () => resolve(null));
|
||||||
|
});
|
||||||
|
let clientProof: string;
|
||||||
|
|
||||||
|
test("Login first phase", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: "/api/v3/auth/login1",
|
||||||
|
body: {
|
||||||
|
email: "[email protected]",
|
||||||
|
clientPublicKey: client.getPublicKey()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("serverPublicKey");
|
||||||
|
expect(payload).toHaveProperty("salt");
|
||||||
|
client.setSalt(payload.salt);
|
||||||
|
client.setServerPublicKey(payload.serverPublicKey);
|
||||||
|
clientProof = client.getProof(); // called M1
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Login second phase", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: "/api/v3/auth/login2",
|
||||||
|
body: {
|
||||||
|
email: testUser.email,
|
||||||
|
clientProof
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("mfaEnabled");
|
||||||
|
expect(payload).toHaveProperty("token");
|
||||||
|
expect(payload.mfaEnabled).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
describe("Status V1 Router", async () => {
|
||||||
|
test("Simple check", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: "/api/status"
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,7 +20,8 @@ export default {
|
|||||||
connection: process.env.DB_CONNECTION_URI,
|
connection: process.env.DB_CONNECTION_URI,
|
||||||
migrations: {
|
migrations: {
|
||||||
directory: path.join(__dirname, "../src/db/migrations"),
|
directory: path.join(__dirname, "../src/db/migrations"),
|
||||||
extension: "ts"
|
extension: "ts",
|
||||||
|
tableName: "infisical_migrations"
|
||||||
},
|
},
|
||||||
seeds: {
|
seeds: {
|
||||||
directory: path.join(__dirname, "../src/db/seeds"),
|
directory: path.join(__dirname, "../src/db/seeds"),
|
||||||
|
|||||||
Generated
+27
@@ -89,6 +89,7 @@
|
|||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
"tsx": "^4.4.0",
|
"tsx": "^4.4.0",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "^5.3.2",
|
||||||
@@ -8175,6 +8176,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
|
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/json5": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"json5": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsonc-parser": {
|
"node_modules/jsonc-parser": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
||||||
@@ -11986,6 +11999,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tsconfig-paths": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"json5": "^2.2.2",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
"tsx": "^4.4.0",
|
"tsx": "^4.4.0",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "^5.3.2",
|
||||||
|
|||||||
@@ -1,10 +1,78 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
import argon2, { argon2id } from "argon2";
|
||||||
import { AuthMethod } from "../../services/auth/auth-type";
|
import { AuthMethod } from "../../services/auth/auth-type";
|
||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { encryptSymmetric } from "@app/lib/crypto";
|
||||||
|
|
||||||
export const testUser = {
|
export const testUser = {
|
||||||
email: "[email protected]"
|
email: "[email protected]",
|
||||||
|
password: process.env.TEST_USER_PASSWORD || "testInfisical@1"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateUserSrpKeys = async (password: string) => {
|
||||||
|
const pair = nacl.box.keyPair();
|
||||||
|
const secretKeyUint8Array = pair.secretKey;
|
||||||
|
const publicKeyUint8Array = pair.publicKey;
|
||||||
|
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||||
|
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const client = new jsrp.client();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
client.init({ username: testUser.email, password: testUser.password }, () => resolve(null));
|
||||||
|
});
|
||||||
|
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
client.createVerifier((err, res) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const derivedKey = await argon2.hash(password, {
|
||||||
|
salt: Buffer.from(salt),
|
||||||
|
memoryCost: 65536,
|
||||||
|
timeCost: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
hashLength: 32,
|
||||||
|
type: argon2id,
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||||
|
|
||||||
|
const key = crypto.randomBytes(32);
|
||||||
|
|
||||||
|
// create encrypted private key by encrypting the private
|
||||||
|
// key with the symmetric key [key]
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag
|
||||||
|
} = encryptSymmetric(privateKey, key.toString("base64"));
|
||||||
|
|
||||||
|
// create the protected key by encrypting the symmetric key
|
||||||
|
// [key] with the derived key
|
||||||
|
const {
|
||||||
|
ciphertext: protectedKey,
|
||||||
|
iv: protectedKeyIV,
|
||||||
|
tag: protectedKeyTag
|
||||||
|
} = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
salt,
|
||||||
|
verifier
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function seed(knex: Knex): Promise<void> {
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
@@ -17,7 +85,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
const [user] = await knex(TableName.Users)
|
const [user] = await knex(TableName.Users)
|
||||||
.insert([
|
.insert([
|
||||||
{
|
{
|
||||||
email: "[email protected]",
|
email: testUser.email,
|
||||||
superAdmin: true,
|
superAdmin: true,
|
||||||
firstName: "test",
|
firstName: "test",
|
||||||
lastName: "",
|
lastName: "",
|
||||||
@@ -30,21 +98,20 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
])
|
])
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
|
const encKeys = await generateUserSrpKeys(testUser.password);
|
||||||
// password: testInfisical@1
|
// password: testInfisical@1
|
||||||
await knex(TableName.UserEncryptionKey).insert([
|
await knex(TableName.UserEncryptionKey).insert([
|
||||||
{
|
{
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey:
|
protectedKey: encKeys.protectedKey,
|
||||||
"Ng0qHLdRdoLR4lHS0xsJVYLR6Y9F44MjkC9dKz1AcGMcoN1VuXjCMySshLPAj2Fboyz9Jo7Qc72YLTaHUGaubA==",
|
protectedKeyIV: encKeys.protectedKeyIV,
|
||||||
protectedKeyIV: "ou9NrOxwQTYJUdIMrQDuPQ==",
|
protectedKeyTag: encKeys.protectedKeyTag,
|
||||||
protectedKeyTag: "BZQMY7mE14maBKzPZpCgtQ==",
|
publicKey: encKeys.publicKey,
|
||||||
publicKey: "rH+riApRZX6HkHMBOhyDhnUBzWWMOx/EBx4gnHYRUTs=",
|
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
||||||
encryptedPrivateKey: "aNnCmeG6sWh7qF40QsFQx9wfgpRTMLtnrtZ+DgkvNXOPYaPW1n2YnH+g20k=",
|
iv: encKeys.encryptedPrivateKeyIV,
|
||||||
iv: "Q4O3elA0iwUvsgSOW0rIkA==",
|
tag: encKeys.encryptedPrivateKeyTag,
|
||||||
tag: "RCuZe9paDKS71hluuX7qbw==",
|
salt: encKeys.salt,
|
||||||
salt: "faac495e264903a7cd42bd75298ddecbf27824f11a744a26788e20b5421ec7e0",
|
verifier: encKeys.verifier,
|
||||||
verifier:
|
|
||||||
"e1f9aff68b4e2c55f21652527bcb070dd171ea2b8de8411890ad13dd7e2da965f29cd5f43a7786690764c3f6233c32c5f98c8da89cd2869bdf940121fff3c25888caa05d8d76c8bb88ddb9c706f2a8f336778c24f64d70f498bd154ac4bf9d79241ed20b343c1fa6d8061cc834401ef89df615bb6e66388dafafad57ed5b334899846d2f031d6134a6681b0cf9ca5ccd13aac53992f564e5550b26222b41ef9c3b6c4e3b8c0ee654762c8feb8a450d4eb9154f7d21b6b409d1027dc298b3240fb7795bae96415dccac689ba705efb82bae469107832bffc703abf6ec532930241061bb88bca3df2ee187ebe06a8f79ccfc851a5f90a83d5703b9ebfc0d08b9ad626870cad09b2ba7ba82c5297f242f5a012bb92c137190fd0168f83f24b62a01431a796b2aba2e133a71106e4271a07c5bdbeeddce5680f785371f5388941e736140b275d6df79cf9f8ce99b6575f7ea4739e7b6fba2f46e53e4bf8e01ca16aecb1138c218fb29b33473515008917f64067ae4104f9eac9abca193ee7596e70de1277a1c789af883d297eb6e72d106ec9899e32d716843ecb8295d1ec54d67b6a754ff5db75ae8233d6472106a7eefb0f9d0b62fe1e2278ec8c4e52e9c65bf2ec2c4e9a20f63c839ce36e67bb5519ef2b425399bad03b47aa0cb5b369444bb13e20d15548da348d6ab852600c35af36f3df8dd1bfc7fa2c00ebf68bc3033da00",
|
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ export const registerRoutes = async (
|
|||||||
await server.register(injectAuditLogInfo);
|
await server.register(injectAuditLogInfo);
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/status",
|
url: "/api/status",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preHandler: (req) => {
|
onRequest: (req, _, done) => {
|
||||||
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
|
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
|
||||||
verifySuperAdmin(req);
|
verifySuperAdmin(req);
|
||||||
|
done();
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const config = await server.services.superAdmin.updateServerCfg(req.body);
|
const config = await server.services.superAdmin.updateServerCfg(req.body);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const superAdminServiceFactory = ({
|
|||||||
const initServerCfg = async () => {
|
const initServerCfg = async () => {
|
||||||
serverCfg = await serverCfgDal.findOne({});
|
serverCfg = await serverCfgDal.findOne({});
|
||||||
if (!serverCfg) {
|
if (!serverCfg) {
|
||||||
const newCfg = await serverCfgDal.create({ initialized: true, allowSignUp: true });
|
const newCfg = await serverCfgDal.create({ initialized: false, allowSignUp: true });
|
||||||
serverCfg = newCfg;
|
serverCfg = newCfg;
|
||||||
return newCfg;
|
return newCfg;
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,7 @@ export const superAdminServiceFactory = ({
|
|||||||
|
|
||||||
const updateServerCfg = async (data: TSuperAdminUpdate) => {
|
const updateServerCfg = async (data: TSuperAdminUpdate) => {
|
||||||
const cfg = await serverCfgDal.updateById(serverCfg.id, data);
|
const cfg = await serverCfgDal.updateById(serverCfg.id, data);
|
||||||
|
serverCfg = cfg;
|
||||||
return cfg;
|
return cfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ export const superAdminServiceFactory = ({
|
|||||||
userAgent
|
userAgent
|
||||||
}: TAdminSignUpDTO) => {
|
}: TAdminSignUpDTO) => {
|
||||||
const existingUser = await userDal.findOne({ email });
|
const existingUser = await userDal.findOne({ email });
|
||||||
if (!existingUser)
|
if (existingUser)
|
||||||
throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
|
throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
|
||||||
|
|
||||||
const userInfo = await userDal.transaction(async (tx) => {
|
const userInfo = await userDal.transaction(async (tx) => {
|
||||||
@@ -68,7 +69,8 @@ export const superAdminServiceFactory = ({
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
email,
|
email,
|
||||||
superAdmin: true
|
superAdmin: true,
|
||||||
|
isAccepted: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
+12
-28
@@ -1,23 +1,19 @@
|
|||||||
{
|
{
|
||||||
|
"ts-node": {
|
||||||
|
// Do not forget to `npm i -D tsconfig-paths`
|
||||||
|
"require": ["tsconfig-paths/register"]
|
||||||
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": [
|
"types": ["vitest/globals"],
|
||||||
"vitest/globals"
|
"typeRoots": ["./node_modules/@types", "./src/@types", "./node_modules"],
|
||||||
],
|
|
||||||
"typeRoots": [
|
|
||||||
"./node_modules/@types",
|
|
||||||
"./src/@types",
|
|
||||||
"./node_modules"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": [
|
"lib": ["esnext"],
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
@@ -26,23 +22,11 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@app/*": [
|
"@app/*": ["./src/*"],
|
||||||
"./src/*"
|
"@lib/*": ["./src/lib/*"],
|
||||||
],
|
"@server/*": ["./src/server/*"]
|
||||||
"@lib/*": [
|
|
||||||
"./src/lib/*"
|
|
||||||
],
|
|
||||||
"@server/*": [
|
|
||||||
"./src/server/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*"],
|
||||||
"src/**/*",
|
"exclude": ["node_modules"]
|
||||||
"scripts/**/*",
|
|
||||||
"e2e-test/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: "./e2e-test/vitest-environment-knex.ts",
|
environment: "./e2e-test/vitest-environment-knex.ts",
|
||||||
include: ["./e2e-test/**/*.spec.ts"]
|
include: ["./e2e-test/**/*.spec.ts"],
|
||||||
|
poolOptions: {
|
||||||
|
threads: {
|
||||||
|
singleThread: true,
|
||||||
|
useAtomics: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [tsconfigPaths()] // only if you are using custom tsconfig paths,
|
plugins: [tsconfigPaths()] // only if you are using custom tsconfig paths,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user