Skip to content

Commit

Permalink
Finish preliminary backwards-compatible transition from user encrypti…
Browse files Browse the repository at this point in the history
…on scheme v1 to v2 with argon2 and protected key
  • Loading branch information
dangtony98 committed Jan 30, 2023
1 parent 5cadb9e commit cf5603c
Show file tree
Hide file tree
Showing 33 changed files with 1,058 additions and 478 deletions.
2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
integrationAuth as v1IntegrationAuthRouter
} from './routes/v1';
import {
signup as v2SignupRouter,
auth as v2AuthRouter,
users as v2UsersRouter,
organizations as v2OrganizationsRouter,
Expand Down Expand Up @@ -110,6 +111,7 @@ app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);

// v2 routes
app.use('/api/v2/signup', v2SignupRouter);
app.use('/api/v2/auth', v2AuthRouter);
app.use('/api/v2/users', v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter);
Expand Down
37 changes: 29 additions & 8 deletions backend/src/controllers/v1/passwordController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,18 @@ export const srp1 = async (req: Request, res: Response) => {
*/
export const changePassword = async (req: Request, res: Response) => {
try {
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } =
req.body;
const {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;

const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
Expand All @@ -192,9 +202,13 @@ export const changePassword = async (req: Request, res: Response) => {
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
Expand Down Expand Up @@ -322,19 +336,26 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
export const resetPassword = async (req: Request, res: Response) => {
try {
const {
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
} = req.body;

await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
Expand Down
208 changes: 3 additions & 205 deletions backend/src/controllers/v1/signupController.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
import { User, MembershipOrg } from '../../models';
import { completeAccount } from '../../helpers/user';
import { User } from '../../models';
import {
sendEmailVerification,
checkEmailVerification,
initializeDefaultOrg
} from '../../helpers/signup';
import { issueTokens, createToken } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import axios from 'axios';
import { createToken } from '../../helpers/auth';

/**
* Signup step 1: Initialize account for user under email [email] and send a verification code
Expand Down Expand Up @@ -102,202 +98,4 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
user,
token
});
};

/**
* Complete setting up user by adding their personal and auth information as part of the
* signup flow
* @param req
* @param res
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
organizationName
} = req.body;

// get user
user = await User.findOne({ email });

if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}

// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
});

if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null

// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
});

// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);

// issue tokens
const tokens = await issueTokens({
userId: user._id.toString()
});

token = tokens.token;
refreshToken = tokens.refreshToken;

// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
await axios.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
},
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}

return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
});
};
/**
* Complete setting up user by adding their personal and auth information as part of the
* invite flow
* @param req
* @param res
* @returns
*/
export const completeAccountInvite = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
} = req.body;

// get user
user = await User.findOne({ email });

if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}

const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
});

if (!membershipOrg) throw new Error('Failed to find invitations for email');

// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
});

if (!user)
throw new Error('Failed to complete account for non-existent user');

// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);

// issue tokens
const tokens = await issueTokens({
userId: user._id.toString()
});

token = tokens.token;
refreshToken = tokens.refreshToken;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}

return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
});
};
};
16 changes: 10 additions & 6 deletions backend/src/controllers/v2/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,11 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
export const login2 = async (req: Request, res: Response) => {

// check to see if user has MFA enabled; if yes then issue MFA-token
// TODO: may have to figure out a better token system for tokens with varying expirations
// (e.g. for org-invitations vs. auth etc.)
try {
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');

if (!user) throw new Error('Failed to find user');

Expand Down Expand Up @@ -142,6 +138,10 @@ export const login2 = async (req: Request, res: Response) => {
// return (access) token in response
return res.status(200).send({
mfaEnabled: false,
encryptionVersion: user.encryptionVersion,
protectedKey: user.protectedKey ?? null,
protectedKeyIV: user.protectedKeyIV ?? null,
protectedKeyTag: user.protectedKeyTag ?? null,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
Expand Down Expand Up @@ -182,7 +182,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {

const user = await User.findOne({
email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');

if (!user) throw new Error('Failed to find user');

Expand All @@ -200,6 +200,10 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
// case: user does not have MFA enabled
// return (access) token in response
return res.status(200).send({
encryptionVersion: user.encryptionVersion,
protectedKey: user.protectedKey ?? null,
protectedKeyIV: user.protectedKeyIV ?? null,
protectedKeyTag: user.protectedKeyTag ?? null,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/controllers/v2/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as authController from './authController';
import * as signupController from './signupController';
import * as usersController from './usersController';
import * as organizationsController from './organizationsController';
import * as workspaceController from './workspaceController';
Expand All @@ -10,6 +11,7 @@ import * as environmentController from './environmentController';

export {
authController,
signupController,
usersController,
organizationsController,
workspaceController,
Expand Down
Loading

0 comments on commit cf5603c

Please sign in to comment.