Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

Endpoints for Webvault #34

Merged
merged 3 commits into from
Sep 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,39 @@ default_cors: &default_cors
allowMethods: "GET, POST, OPTIONS, PUT, DELETE"

functions:
# Identity endpoints

login:
handler: src/login.handler
events:
- http:
method: post
<<: *default_cors
path: identity/connect/token

# Accounts endpoints

prelogin:
handler: src/prelogin.handler
events:
- http:
method: post
<<: *default_cors
path: api/accounts/prelogin
login:
handler: src/login.handler
profile:
handler: src/accounts.profileHandler
events:
- http:
method: post
method: get
<<: *default_cors
path: identity/connect/token
path: api/accounts/profile
profile_put:
handler: src/accounts.putProfileHandler
events:
- http:
method: put
<<: *default_cors
path: api/accounts/profile
keys:
handler: src/keys.handler
events:
Expand All @@ -84,6 +103,9 @@ functions:
method: get
<<: *default_cors
path: api/accounts/revision-date

# Other endpoints, ciphers and data

sync:
handler: src/sync.handler
events:
Expand Down
46 changes: 45 additions & 1 deletion src/accounts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
import * as utils from './lib/api_utils';
import { loadContextFromHeader } from './lib/bitwarden';
import { getRevisionDateAsMillis } from './lib/mappers';
import { getRevisionDateAsMillis, mapUser } from './lib/mappers';

export const profileHandler = async (event, context, callback) => {
console.log('Account profile handler triggered', JSON.stringify(event, null, 2));

let user;
try {
({ user } = await loadContextFromHeader(event.headers.Authorization));
} catch (e) {
callback(null, utils.validationError('User not found: ' + e.message));
}

try {
callback(null, utils.okResponse(mapUser(user)));
} catch (e) {
callback(null, utils.serverError(e.toString()));
}
};

export const putProfileHandler = async (event, context, callback) => {
console.log('Update account profile handler triggered', JSON.stringify(event, null, 2));

let user;
try {
({ user } = await loadContextFromHeader(event.headers.Authorization));
} catch (e) {
callback(null, utils.validationError('User not found: ' + e.message));
}

const body = utils.normalizeBody(JSON.parse(event.body));

[['masterpasswordhint', 'passwordHint'], ['name', 'name'], ['culture', 'culture']].forEach(([requestAttr, attr]) => {
if (body[requestAttr]) {
user.set({ [attr]: body[requestAttr] });
}
});

try {
user = await user.updateAsync();

callback(null, utils.okResponse(mapUser(user)));
} catch (e) {
callback(null, utils.serverError(e.toString()));
}
};

export const revisionDateHandler = async (event, context, callback) => {
console.log('Account revision date handler triggered', JSON.stringify(event, null, 2));
Expand Down
4 changes: 2 additions & 2 deletions src/lib/mappers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ export function mapCipher(cipher) {
export function mapUser(user) {
return {
Id: user.get('uuid'),
Name: null,
Name: user.get('name'),
Email: user.get('email'),
EmailVerified: user.get('emailVerified'),
Premium: user.get('premium'),
MasterPasswordHint: user.get('passwordHint'),
Culture: user.get('culture'),
TwoFactorEnabled: !!user.get('totpSecret'),
Key: user.get('key'),
PrivateKey: null,
PrivateKey: (user.get('privateKey') || '').toString('utf8'),
SecurityStamp: user.get('securityStamp'),
Organizations: [],
Object: 'profile',
Expand Down
2 changes: 1 addition & 1 deletion src/lib/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const User = dynogels.define('User', {
email: Joi.string().email().required(),
emailVerified: Joi.boolean(),
premium: Joi.boolean(),
name: Joi.string(),
name: Joi.string().allow(null),
passwordHash: Joi.string().required(),
passwordHint: Joi.string().allow(null),
key: Joi.string(),
Expand Down
54 changes: 46 additions & 8 deletions test/e2e/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ describe("Accounts API", function () {
};
}

let bearerToken;
let email;

it("should return tokens if authentication successful", function () {
before(async function() {
var registrationBody = getRegistrationBody();
var loginBody = getLoginBody();
loginBody.username = registrationBody.email;
email = registrationBody.email;

return chakram.post(
process.env.API_URL + "/api/accounts/register",
Expand All @@ -44,19 +47,54 @@ describe("Accounts API", function () {
form: loginBody
}
);

return
}).then(function (response) {
return chakram.get(
process.env.API_URL + "/api/accounts/revision-date",
{ headers: { Authorization: 'Bearer ' + response.body.access_token } }
);
}).then(function (response) {
bearerToken = response.body.access_token;
});
})

it("should return account revision date", function () {
return chakram.get(
process.env.API_URL + "/api/accounts/revision-date",
{ headers: { Authorization: 'Bearer ' + bearerToken } }
).then(function (response) {
var body = response.body;
expect(response).to.have.status(200);
expect(response.body).to.be.at.least((new Date()).getTime() - (20 * 1000));

return chakram.wait();
});
});

it("should return user profile", function () {
return chakram.get(
process.env.API_URL + "/api/accounts/profile",
{ headers: { Authorization: 'Bearer ' + bearerToken } }
).then(function (response) {
var body = response.body;
expect(response).to.have.status(200);
expect(response.body.Email).to.equal(email);

return chakram.wait();
});
});

it("should return update user profile", function () {
return chakram.put(
process.env.API_URL + "/api/accounts/profile",
{
name: 'newname',
culture: 'cs-CZ',
masterPasswordHint: 'newhint',
},
{ headers: { Authorization: 'Bearer ' + bearerToken } }
).then(function (response) {
var body = response.body;
expect(response).to.have.status(200);
expect(body.Name).to.equal('newname');
expect(body.Culture).to.equal('cs-CZ');
expect(body.MasterPasswordHint).to.equal('newhint');

return chakram.wait();
});
});
});