feat: added login test and fixed super admin bug

This commit is contained in:
Akhil Mohan
2024-01-14 14:16:30 +05:30
parent c7572a3374
commit dd7c449483
11 changed files with 192 additions and 49 deletions

View File

@@ -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: "test@localhost.local",
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();
});
});

View File

@@ -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);
});
});

View File

@@ -20,7 +20,8 @@ export default {
connection: process.env.DB_CONNECTION_URI,
migrations: {
directory: path.join(__dirname, "../src/db/migrations"),
extension: "ts"
extension: "ts",
tableName: "infisical_migrations"
},
seeds: {
directory: path.join(__dirname, "../src/db/seeds"),

View File

@@ -89,6 +89,7 @@
"prompt-sync": "^4.2.0",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tsup": "^8.0.1",
"tsx": "^4.4.0",
"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",
"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": {
"version": "3.2.0",
"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": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",

View File

@@ -58,6 +58,7 @@
"prompt-sync": "^4.2.0",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tsup": "^8.0.1",
"tsx": "^4.4.0",
"typescript": "^5.3.2",

View File

@@ -1,10 +1,78 @@
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 { TableName } from "../schemas";
import crypto from "node:crypto";
import { encryptSymmetric } from "@app/lib/crypto";
export const testUser = {
email: "test@localhost.local"
email: "test@localhost.local",
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> {
@@ -17,7 +85,7 @@ export async function seed(knex: Knex): Promise<void> {
const [user] = await knex(TableName.Users)
.insert([
{
email: "test@localhost.local",
email: testUser.email,
superAdmin: true,
firstName: "test",
lastName: "",
@@ -30,21 +98,20 @@ export async function seed(knex: Knex): Promise<void> {
])
.returning("*");
const encKeys = await generateUserSrpKeys(testUser.password);
// password: testInfisical@1
await knex(TableName.UserEncryptionKey).insert([
{
encryptionVersion: 2,
protectedKey:
"Ng0qHLdRdoLR4lHS0xsJVYLR6Y9F44MjkC9dKz1AcGMcoN1VuXjCMySshLPAj2Fboyz9Jo7Qc72YLTaHUGaubA==",
protectedKeyIV: "ou9NrOxwQTYJUdIMrQDuPQ==",
protectedKeyTag: "BZQMY7mE14maBKzPZpCgtQ==",
publicKey: "rH+riApRZX6HkHMBOhyDhnUBzWWMOx/EBx4gnHYRUTs=",
encryptedPrivateKey: "aNnCmeG6sWh7qF40QsFQx9wfgpRTMLtnrtZ+DgkvNXOPYaPW1n2YnH+g20k=",
iv: "Q4O3elA0iwUvsgSOW0rIkA==",
tag: "RCuZe9paDKS71hluuX7qbw==",
salt: "faac495e264903a7cd42bd75298ddecbf27824f11a744a26788e20b5421ec7e0",
verifier:
"e1f9aff68b4e2c55f21652527bcb070dd171ea2b8de8411890ad13dd7e2da965f29cd5f43a7786690764c3f6233c32c5f98c8da89cd2869bdf940121fff3c25888caa05d8d76c8bb88ddb9c706f2a8f336778c24f64d70f498bd154ac4bf9d79241ed20b343c1fa6d8061cc834401ef89df615bb6e66388dafafad57ed5b334899846d2f031d6134a6681b0cf9ca5ccd13aac53992f564e5550b26222b41ef9c3b6c4e3b8c0ee654762c8feb8a450d4eb9154f7d21b6b409d1027dc298b3240fb7795bae96415dccac689ba705efb82bae469107832bffc703abf6ec532930241061bb88bca3df2ee187ebe06a8f79ccfc851a5f90a83d5703b9ebfc0d08b9ad626870cad09b2ba7ba82c5297f242f5a012bb92c137190fd0168f83f24b62a01431a796b2aba2e133a71106e4271a07c5bdbeeddce5680f785371f5388941e736140b275d6df79cf9f8ce99b6575f7ea4739e7b6fba2f46e53e4bf8e01ca16aecb1138c218fb29b33473515008917f64067ae4104f9eac9abca193ee7596e70de1277a1c789af883d297eb6e72d106ec9899e32d716843ecb8295d1ec54d67b6a754ff5db75ae8233d6472106a7eefb0f9d0b62fe1e2278ec8c4e52e9c65bf2ec2c4e9a20f63c839ce36e67bb5519ef2b425399bad03b47aa0cb5b369444bb13e20d15548da348d6ab852600c35af36f3df8dd1bfc7fa2c00ebf68bc3033da00",
protectedKey: encKeys.protectedKey,
protectedKeyIV: encKeys.protectedKeyIV,
protectedKeyTag: encKeys.protectedKeyTag,
publicKey: encKeys.publicKey,
encryptedPrivateKey: encKeys.encryptedPrivateKey,
iv: encKeys.encryptedPrivateKeyIV,
tag: encKeys.encryptedPrivateKeyTag,
salt: encKeys.salt,
verifier: encKeys.verifier,
userId: user.id
}
]);

View File

@@ -472,7 +472,7 @@ export const registerRoutes = async (
await server.register(injectAuditLogInfo);
server.route({
url: "/status",
url: "/api/status",
method: "GET",
schema: {
response: {

View File

@@ -37,9 +37,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
})
}
},
preHandler: (req) => {
onRequest: (req, _, done) => {
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req);
verifySuperAdmin(req);
done();
},
handler: async (req) => {
const config = await server.services.superAdmin.updateServerCfg(req.body);

View File

@@ -24,7 +24,7 @@ export const superAdminServiceFactory = ({
const initServerCfg = async () => {
serverCfg = await serverCfgDal.findOne({});
if (!serverCfg) {
const newCfg = await serverCfgDal.create({ initialized: true, allowSignUp: true });
const newCfg = await serverCfgDal.create({ initialized: false, allowSignUp: true });
serverCfg = newCfg;
return newCfg;
}
@@ -39,6 +39,7 @@ export const superAdminServiceFactory = ({
const updateServerCfg = async (data: TSuperAdminUpdate) => {
const cfg = await serverCfgDal.updateById(serverCfg.id, data);
serverCfg = cfg;
return cfg;
};
@@ -59,7 +60,7 @@ export const superAdminServiceFactory = ({
userAgent
}: TAdminSignUpDTO) => {
const existingUser = await userDal.findOne({ email });
if (!existingUser)
if (existingUser)
throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
const userInfo = await userDal.transaction(async (tx) => {
@@ -68,7 +69,8 @@ export const superAdminServiceFactory = ({
firstName,
lastName,
email,
superAdmin: true
superAdmin: true,
isAccepted: true
},
tx
);

View File

@@ -1,23 +1,19 @@
{
"ts-node": {
// Do not forget to `npm i -D tsconfig-paths`
"require": ["tsconfig-paths/register"]
},
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"allowJs": true,
"resolveJsonModule": true,
"types": [
"vitest/globals"
],
"typeRoots": [
"./node_modules/@types",
"./src/@types",
"./node_modules"
],
"types": ["vitest/globals"],
"typeRoots": ["./node_modules/@types", "./src/@types", "./node_modules"],
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": [
"esnext"
],
"lib": ["esnext"],
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"experimentalDecorators": true,
@@ -26,23 +22,11 @@
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@app/*": [
"./src/*"
],
"@lib/*": [
"./src/lib/*"
],
"@server/*": [
"./src/server/*"
]
"@app/*": ["./src/*"],
"@lib/*": ["./src/lib/*"],
"@server/*": ["./src/server/*"]
}
},
"include": [
"src/**/*",
"scripts/**/*",
"e2e-test/**/*"
],
"exclude": [
"node_modules"
]
"include": ["src/**/*", "scripts/**/*", "e2e-test/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -5,7 +5,13 @@ export default defineConfig({
test: {
globals: true,
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,
});