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

Password policy improvements #589

Merged
merged 10 commits into from
Dec 23, 2024
48 changes: 40 additions & 8 deletions keycloak/themes/uid2-theme/login/login-update-password.ftl
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
<#import "template.ftl" as layout>

<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section>
<#if section = "header">
${msg("updatePasswordTitle")}
<div id="password-error-message" class="kcErrorMessage" style="display:none;">
<p class="error-text"></p>
</div>
<#elseif section = "form">
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post" onsubmit="return checkPasswordBlacklist()">
<input type="text" id="username" name="username" value="${username}" autocomplete="username"
readonly="readonly" style="display:none;"/>
<input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/>
<div id="password-policy"> ${msg("passwordPolicy")}</div>
<div id="set-password"> ${msg("setPassword")}</div>
<ul id="password-requirements">
<li>${msg("passwordMinLength")}</li>
<li>${msg("passwordNoEmail")}</li>
<li>${msg("passwordNoPrevious")}</li>
</ul>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>
Expand Down Expand Up @@ -70,6 +67,41 @@
</#if>
</div>
</div>


<script type="text/javascript">
let blacklistedPasswords = [];

function loadBlacklist() {
fetch('https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulling from this file, but we can also store the txt file in our code if that makes sense too. i went with this option because i didnt want to take up space in the code for a huge file, but it would be an issue if this URL changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love relying on a 3rd party to be up in order for our blacklist to work. You can just save the file locally and load it from there, too.

.then(response => response.text())
.then(data => {
blacklistedPasswords = data.split("\n");
blacklistedPasswords = blacklistedPasswords.filter(password => password.length >= 8);
})
.catch(error => {
console.error("could not get blacklist", error);
});
}

loadBlacklist();

function checkPasswordBlacklist() {
var password = document.getElementById("password-new").value;

if (blacklistedPasswords.includes(password)) {
var errorMessageDiv = document.getElementById("password-error-message");
var errorText = document.querySelector(".kcErrorMessage .error-text");
errorText.textContent = "Password is commonly used.";
errorMessageDiv.style.display = "block";
return false;
}

var errorMessageDiv = document.getElementById("password-error-message");
errorMessageDiv.style.display = "none";
return true;
}
</script>
</form>
</#if>
</@layout.registrationLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ errorPatternNoMatch=We’re sorry, but we only accept sign-ups from company emai
forgotPasswordInfo=Enter your email address and we’ll send a link to reset your password.
doSendLink=Request Password Reset
emailForgotTitle=Forgot Password
updatePasswordTitle=Set Password
updatePasswordTitle=Set New Password Required
doUpdatePassword=Save Password
passwordConfirm=Confirm Password
verifyEmailMessage=To activate your account, verify your email address.
emailVerifyInstruction2=Haven’t received the verification email?
confirmExecutionOfActions=Please do the following:
proceedWithAction=Continue
accountUpdatedMessage=Your account has been approved
accountUpdatedInstruction=You can now log in to the UID2 Portal.
passwordPolicy=Our password requirements have changed.
setPassword=Please set or update your password to match the following criteria.
passwordMinLength=Must be at least 8 characters.
passwordNoEmail=Cannot match your email address.
passwordNoPrevious=Cannot be a password you’ve used before on the UID2 Portal.
accountUpdatedInstruction=You can now log in to the UID2 Portal.
15 changes: 15 additions & 0 deletions keycloak/themes/uid2-theme/login/resources/css/login.css
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,19 @@ div.kc-logo-text {
margin-top: 20px;
}

.kcErrorMessage {
margin-top: 15px;
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 5px;
display: none; /* Hidden by default */
}

.kcErrorMessage p {
margin: 0;
font-size: 14px;
}

/* End Recovery codes */
3 changes: 2 additions & 1 deletion keycloak/themes/uid2-theme/login/template.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
<div class="centralize-content">
<#-- App-initiated actions should not see warning messages about the need to complete the action -->
<#-- during login. -->
<#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
<br />
<#if displayMessage && message?has_content && message.type != 'warning' && !isAppInitiatedAction??>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just taking out the parentheses fixed the logic here and no longer shows the "activate your account" message but still shows the error messages if someone inputs the wrong password

<div class="alert-${message.type} ${properties.kcAlertClass!} pf-m-<#if message.type = 'error'>danger<#else>${message.type}</#if>">
<div class="pf-c-alert__icon">
<#if message.type = 'success'><span class="${properties.kcFeedbackSuccessIcon!}"></span></#if>
Expand Down
3 changes: 1 addition & 2 deletions src/web/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useKeycloak } from '@react-keycloak/web';
import { StrictMode, useCallback, useContext } from 'react';
import { Outlet, useLocation } from 'react-router-dom';

Check warning on line 3 in src/web/App.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useLocation' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/web/App.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useLocation' is defined but never used. Allowed unused vars must match /^_/u

import { EnvironmentBanner } from './components/Core/Banner/EnvironmentBanner';
import { ErrorView } from './components/Core/ErrorView/ErrorView';
Expand All @@ -24,12 +24,11 @@
const { LoggedInUser } = useContext(CurrentUserContext);
const { participant } = useContext(ParticipantContext);
const isLocalDev = process.env.NODE_ENV === 'development';
const location = useLocation();

if (LoggedInUser?.user?.participants!.length === 0) {
return <ErrorView message='You do not have access to any participants.' />;
}
if (location.pathname !== '/account/create' && LoggedInUser && !participant) {
if (LoggedInUser && !participant) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this account/create page does not exist anymore and should be removed. randomly found this when testing stuff

return <NoParticipantAccessView user={LoggedInUser?.user} />;
}

Expand Down
8 changes: 0 additions & 8 deletions src/web/contexts/CurrentUserProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useKeycloak } from '@react-keycloak/web';
import { createContext, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

Check warning on line 3 in src/web/contexts/CurrentUserProvider.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useLocation' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/web/contexts/CurrentUserProvider.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useNavigate' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/web/contexts/CurrentUserProvider.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useLocation' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/web/contexts/CurrentUserProvider.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

'useNavigate' is defined but never used. Allowed unused vars must match /^_/u

import { Loading } from '../components/Core/Loading/Loading';
import { GetLoggedInUserAccount, UserAccount } from '../services/userAccount';
Expand All @@ -21,8 +21,6 @@
const { keycloak } = useKeycloak();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [LoggedInUser, SetLoggedInUser] = useState<UserAccount | null>(null);
const location = useLocation();
const navigate = useNavigate();
const throwError = useAsyncThrowError();

const loadUser = useCallback(async () => {
Expand All @@ -41,12 +39,6 @@
}
}, [keycloak, throwError]);

useEffect(() => {
if (LoggedInUser && !LoggedInUser.user && location.pathname !== '/account/create') {
navigate('/account/create');
}
}, [LoggedInUser, location.pathname, navigate]);

useEffect(() => {
if (keycloak.token) {
loadUser();
Expand Down
Loading