Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#9547): add password change feature on first time login #9581

Open
wants to merge 52 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d7173a6
chore: test password change routing
Benmuiruri Oct 24, 2024
523fc30
Add password reset flag to API
Benmuiruri Oct 29, 2024
1ef9996
chore: add password validation
Benmuiruri Oct 30, 2024
efe3a72
chore: add password validation error msgs
Benmuiruri Oct 30, 2024
057782c
chore: prevent unauthorized access before password change
Benmuiruri Oct 30, 2024
63017ca
chore: redirect back to login if not password reset
Benmuiruri Oct 30, 2024
bfa9485
chore: use cookie to prevent access
Benmuiruri Oct 31, 2024
c9b570d
chore: try cookie to show UI
Benmuiruri Oct 31, 2024
f1dea9a
chore: show password success UI
Benmuiruri Oct 31, 2024
7e15098
sonar reduce cognitive
Benmuiruri Oct 31, 2024
d2d18e9
chore: sonar fixes
Benmuiruri Nov 1, 2024
9d1c055
Merge branch 'master' of github.com:medic/cht-core into 9547-change-p…
Benmuiruri Nov 4, 2024
8fee9e8
chore: interpolate translations
Benmuiruri Nov 4, 2024
0323df5
chore: first round of feedback
Benmuiruri Nov 4, 2024
935c721
chore: additional feedback
Benmuiruri Nov 4, 2024
f4f4de0
chore: refactor password validation
Benmuiruri Nov 6, 2024
04b3f97
chore: refactor toggle password and legacy code
Benmuiruri Nov 6, 2024
1a4e971
chore: remove passwordUpdate cookie and set minimal cookie without Auth
Benmuiruri Nov 6, 2024
f6d9949
chore: refactor setting basic Cookie
Benmuiruri Nov 7, 2024
d693b75
chore: clean password reset
Benmuiruri Nov 7, 2024
01acf08
chore: fix userService unit test
Benmuiruri Nov 7, 2024
a4ea9a6
chore: add fr translations
Benmuiruri Nov 7, 2024
13092d1
Merge branch 'master' of github.com:medic/cht-core into 9547-change-p…
Benmuiruri Nov 8, 2024
ecc3f82
chore: refactor to initial flow
Benmuiruri Nov 8, 2024
20cde88
chore: simplify check
Benmuiruri Nov 8, 2024
74ed3e2
chore: add ne translations
Benmuiruri Nov 9, 2024
fcf73bd
chore: use getUserDoc not getUserCtx
Benmuiruri Nov 9, 2024
b3ec3f0
chore: refactor password reset, add password reset unit test
Benmuiruri Nov 10, 2024
c15cced
chore: refactor password validation
Benmuiruri Nov 11, 2024
3c0f2fa
chore: set password_change_required on password update
Benmuiruri Nov 11, 2024
5fcb64d
chore: update e2e to do password reset
Benmuiruri Nov 11, 2024
1f223f8
Add password reset e2e
Benmuiruri Nov 12, 2024
5a24839
disable eslint to run tests in ci
Benmuiruri Nov 12, 2024
ff6192c
try api eslint
Benmuiruri Nov 12, 2024
4817bb1
chore: add unit tests
Benmuiruri Nov 12, 2024
47d990b
chore: skip password reset for admin
Benmuiruri Nov 12, 2024
0a814c6
chore: self review
Benmuiruri Nov 12, 2024
2bc1603
chore: update service worker unit test
Benmuiruri Nov 12, 2024
3e6fd03
chore: update integration tests and fix login e2e
Benmuiruri Nov 13, 2024
dd73991
chore: sonar
Benmuiruri Nov 13, 2024
b48c967
Merge branch 'master' of github.com:medic/cht-core into 9547-change-p…
Benmuiruri Nov 13, 2024
f649876
chore: add selector specificity
Benmuiruri Nov 13, 2024
8511c67
address feedback
Benmuiruri Nov 14, 2024
cbde20f
validate user changing password
Benmuiruri Nov 15, 2024
71ccda5
chore: try iife pattern
Benmuiruri Nov 15, 2024
cf77f9f
chore: sonar
Benmuiruri Nov 15, 2024
6c7d976
validate password reset in auth middleware
Benmuiruri Nov 15, 2024
e0806e0
update e2e
Benmuiruri Nov 18, 2024
95d5f9c
Merge branch 'master' of github.com:medic/cht-core into 9547-change-p…
Benmuiruri Nov 18, 2024
40bee0a
chore: update breaking e2e
Benmuiruri Nov 18, 2024
87170ff
chore: sonar
Benmuiruri Nov 18, 2024
c23d3e2
chore: update e2e tests
Benmuiruri Nov 18, 2024
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
44 changes: 41 additions & 3 deletions api/src/controllers/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ const templates = {
'privacy.policy'
],
},
passwordReset: {
file: path.join(__dirname, '..', 'templates', 'login', 'password-reset.html'),
translationStrings: [
'login',
'Password',
'privacy.policy'
],
}
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved
};

let password_change_required = true;

const checkPasswordChange = async (userCtx) => {
return await auth.hasAllPermissions(userCtx, 'can_change_password_first_login');
};

const getHomeUrl = userCtx => {
Expand Down Expand Up @@ -300,15 +314,27 @@ const renderLogin = (req) => {
return render('login', req);
};

const renderPasswordReset = (req) => {
return render('passwordReset', req);
}

const login = async (req, res) => {
try {
const sessionRes = await createSession(req);
if (sessionRes.statusCode !== 200) {
res.status(sessionRes.statusCode).json({ error: 'Not logged in' });
} else {
const redirectUrl = await setCookies(req, res, sessionRes);
res.status(302).send(redirectUrl);
}
const sessionCookie = getSessionCookie(sessionRes);
const options = { headers: { Cookie: sessionCookie } };
const userCtx = await getUserCtxRetry(options);
await setCookies(req, res, sessionRes);
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved

const needsPasswordChange = await checkPasswordChange(userCtx);
if (needsPasswordChange && password_change_required) {
return res.status(302).send('/medic/password-reset');
}
const redirectUrl = getRedirectUrl(userCtx, req.body.redirect);
res.status(302).send(redirectUrl);
} catch (e) {
if (e.status === 401) {
return res.status(401).json({ error: e.error });
Expand All @@ -320,6 +346,7 @@ const login = async (req, res) => {

module.exports = {
renderLogin,
renderPasswordReset,

get: (req, res, next) => {
return renderLogin(req)
Expand Down Expand Up @@ -355,6 +382,17 @@ module.exports = {
});
},

passwordResetGet: (req, res, next) => {
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved
return renderPasswordReset(req)
.then(body => {
res.setHeader(
'Link',
'</login/style.css>; rel=preload; as=style, '
);
res.send(body);
})
.catch(next);
},
tokenGet: (req, res, next) => renderTokenLogin(req, res).catch(next),
tokenPost: async (req, res, next) => {
const limited = await rateLimitService.isLimited(req);
Expand Down
5 changes: 5 additions & 0 deletions api/src/generate-service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const getLoginPageContents = async () => {
return await loginController.renderLogin();
};

const getPasswordResetPageContents = async () => {
return await loginController.renderPasswordReset();
}

const appendExtensionLibs = async (config) => {
const libs = await extensionLibs.getAll();
// cache this even if there are no libs so offline client knows there are no libs
Expand Down Expand Up @@ -99,6 +103,7 @@ const writeServiceWorkerFile = async () => {
templatedURLs: {
'/': ['webapp/index.html'], // Webapp's entry point
'/medic/login': await getLoginPageContents(),
'/medic/password-reset': await getPasswordResetPageContents(),
'/medic/_design/medic/_rewrite/': ['webapp/appcache-upgrade.html']
},
ignoreURLParametersMatching: [/redirect/, /username/],
Expand Down
1 change: 1 addition & 0 deletions api/src/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ app.get(routePrefix + 'login', login.get);
app.get(routePrefix + 'login/identity', login.getIdentity);
app.postJson(routePrefix + 'login', login.post);
app.get(routePrefix + 'login/token/:token?', login.tokenGet);
app.get(routePrefix + 'password-reset', login.passwordResetGet);
app.postJson(routePrefix + 'login/token/:token?', login.tokenPost);
app.get(routePrefix + 'privacy-policy', privacyPolicyController.get);

Expand Down
16 changes: 16 additions & 0 deletions api/src/templates/login/password-reset.html
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#323232">
<title>{{ branding.name }}</title>
<link href="/login/style.css" media="all" rel="stylesheet">
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<div class="center">
<h1>Password Reset</h1>
<p>You need to reset your password before continuing.</p>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions config/default/app_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"can_view_old_navigation": [],
"can_default_facility_filter": [],
"can_have_multiple_places": [],
"can_change_password_first_login": [],
"can_export_devices_details": [
"national_admin"
]
Expand Down
Loading