diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/index.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/index.html.j2 new file mode 100644 index 0000000..2f10a8e --- /dev/null +++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/index.html.j2 @@ -0,0 +1,67 @@ +{% extends "/dashboard/_base.html" %} {% block content %} +
+
+

+ Change Password +

+ {% if errors | list %} + + {% endif %} + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+ +{% endblock %} diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/invalid_password.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/invalid_password.html.j2 new file mode 100644 index 0000000..d846cad --- /dev/null +++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/invalid_password.html.j2 @@ -0,0 +1,37 @@ +
+
+ + +
+
+ + +
+ +

Oops! Passwords do not match!

+ +
+ +
+ +
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/success.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/success.html.j2 new file mode 100644 index 0000000..748faec --- /dev/null +++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/success.html.j2 @@ -0,0 +1,12 @@ +{% extends "/dashboard/_base.html" %} {% block content %} +
+
+

Password Changed

+

Your password was changed sucessfully. Next time you log in, use your new password.

+ + + Go back to the dashboard + +
+
+{% endblock content %} diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/valid_password.html.j2 b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/valid_password.html.j2 new file mode 100644 index 0000000..d90aeb6 --- /dev/null +++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/change_password/valid_password.html.j2 @@ -0,0 +1,33 @@ +
+
+ + +
+
+ + +
+
+ +
+
diff --git a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/dashboard/navbar.html b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/dashboard/navbar.html index bc83b13..6f86f14 100644 --- a/packages/sshecret-admin/src/sshecret_admin/frontend/templates/dashboard/navbar.html +++ b/packages/sshecret-admin/src/sshecret_admin/frontend/templates/dashboard/navbar.html @@ -94,7 +94,7 @@ {% if user.local %}
  • Change Password StatsView: """Get stats for the frontpage.""" clients = await admin.get_clients() @@ -75,4 +89,102 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter: }, ) + @app.get("/password") + async def get_change_password( + request: Request, + current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)], + ): + """Render Change password site.""" + if not current_user.local: + LOG.debug("User tried to change password, but is not a local user.") + return RedirectException(to=URL("/")) + + return templates.TemplateResponse( + request, + "change_password/index.html.j2", + { + "page_title": "Change Password", + "user": current_user, + "errors": [], + }, + ) + + @app.post("/password") + async def change_password( + request: Request, + current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)], + admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)], + session: Annotated[AsyncSession, Depends(dependencies.get_async_session)], + passwd_form: Annotated[PasswordChangeForm, Form()], + ): + """Change password.""" + errors: list[str] = [] + user = await authenticate_user_async( + session, current_user.display_name, passwd_form.current_password + ) + new_password_matches = passwd_form.password == passwd_form.confirm_password + if not user: + errors.append("Invalid current password entered") + if not new_password_matches: + errors.append("Passwords do not match") + + if errors: + return templates.TemplateResponse( + request, + "change_password/index.html.j2", + { + "page_title": "Change Password", + "user": current_user, + "errors": errors, + }, + ) + + assert user is not None + new_password_hash = hash_password(passwd_form.password) + user.hashed_password = new_password_hash + session.add(user) + await session.commit() + origin = "UNKNOWN" + if request.client: + origin = request.client.host + await admin.write_audit_message( + Operation.UPDATE, + "User changed their password", + origin, + username=user.username, + ) + + return templates.TemplateResponse( + request, + "change_password/success.html.j2", + { + "page_title": "Change Password success", + "user": current_user, + }, + ) + + @app.post("/password/validate-confirm") + async def validate_password_match( + request: Request, + password: Annotated[str, Form()], + confirm_password: Annotated[str, Form()], + ): + """Validate password matches.""" + valid = "/change_password/valid_password.html.j2" + invalid = "/change_password/invalid_password.html.j2" + template = valid + if password != confirm_password: + template = invalid + + LOG.info("Password matches: %r", (password == confirm_password)) + + return templates.TemplateResponse( + request, + template, + { + "password": password, + "confirm_password": confirm_password, + }, + ) + return app diff --git a/packages/sshecret-admin/src/sshecret_admin/static/css/main.css b/packages/sshecret-admin/src/sshecret_admin/static/css/main.css index 0157edf..05fd108 100644 --- a/packages/sshecret-admin/src/sshecret_admin/static/css/main.css +++ b/packages/sshecret-admin/src/sshecret_admin/static/css/main.css @@ -25,11 +25,13 @@ --color-orange-800: oklch(47% 0.157 37.304); --color-lime-400: oklch(84.1% 0.238 128.85); --color-lime-500: oklch(76.8% 0.233 130.85); + --color-green-50: oklch(98.2% 0.018 155.826); --color-green-100: oklch(96.2% 0.044 156.743); --color-green-200: oklch(92.5% 0.084 155.995); --color-green-400: oklch(79.2% 0.209 151.711); --color-green-500: oklch(72.3% 0.219 149.579); --color-green-600: oklch(62.7% 0.194 149.214); + --color-green-700: oklch(52.7% 0.154 150.069); --color-green-800: oklch(44.8% 0.119 151.328); --color-green-900: oklch(39.3% 0.095 152.535); --color-emerald-500: oklch(69.6% 0.17 162.48); @@ -413,9 +415,6 @@ .m-361 { margin: calc(var(--spacing) * 361); } - .mx-2 { - margin-inline: calc(var(--spacing) * 2); - } .mx-3 { margin-inline: calc(var(--spacing) * 3); } @@ -443,12 +442,6 @@ .my-10 { margin-block: calc(var(--spacing) * 10); } - .my-\[0\.5rem\] { - margin-block: 0.5rem; - } - .my-\[1rem\] { - margin-block: 1rem; - } .my-auto { margin-block: auto; } @@ -500,6 +493,9 @@ .mt-8 { margin-top: calc(var(--spacing) * 8); } + .mt-\[2px\] { + margin-top: 2px; + } .-mr-1 { margin-right: calc(var(--spacing) * -1); } @@ -593,9 +589,6 @@ .box-border { box-sizing: border-box; } - .box-content { - box-sizing: content-box; - } .block { display: block; } @@ -779,9 +772,6 @@ .w-full { width: 100%; } - .w-max { - width: max-content; - } .max-w-2xl { max-width: var(--container-2xl); } @@ -794,9 +784,6 @@ .max-w-lg { max-width: var(--container-lg); } - .max-w-max { - max-width: max-content; - } .max-w-md { max-width: var(--container-md); } @@ -879,12 +866,18 @@ .animate-spin { animation: var(--animate-spin); } + .cursor-not-allowed { + cursor: not-allowed; + } .cursor-pointer { cursor: pointer; } .resize { resize: both; } + .list-inside { + list-style-position: inside; + } .list-disc { list-style-type: disc; } @@ -1218,6 +1211,9 @@ .border-green-100 { border-color: var(--color-green-100); } + .border-green-500 { + border-color: var(--color-green-500); + } .border-orange-100 { border-color: var(--color-orange-100); } @@ -1230,6 +1226,9 @@ .border-red-300 { border-color: var(--color-red-300); } + .border-red-500 { + border-color: var(--color-red-500); + } .border-red-600 { border-color: var(--color-red-600); } @@ -1296,9 +1295,6 @@ .bg-gray-200 { background-color: var(--color-gray-200); } - .bg-gray-700 { - background-color: var(--color-gray-700); - } .bg-gray-800 { background-color: var(--color-gray-800); } @@ -1311,6 +1307,9 @@ background-color: color-mix(in oklab, var(--color-gray-900) 50%, transparent); } } + .bg-green-50 { + background-color: var(--color-green-50); + } .bg-green-100 { background-color: var(--color-green-100); } @@ -1320,9 +1319,6 @@ .bg-green-400 { background-color: var(--color-green-400); } - .bg-indigo-200 { - background-color: var(--color-indigo-200); - } .bg-indigo-600 { background-color: var(--color-indigo-600); } @@ -1389,9 +1385,6 @@ .bg-teal-100 { background-color: var(--color-teal-100); } - .bg-teal-700 { - background-color: var(--color-teal-700); - } .bg-transparent { background-color: transparent; } @@ -1455,9 +1448,6 @@ .px-6 { padding-inline: calc(var(--spacing) * 6); } - .px-\[1\.125rem\] { - padding-inline: 1.125rem; - } .py-0\.5 { padding-block: calc(var(--spacing) * 0.5); } @@ -1747,6 +1737,9 @@ .text-green-800 { color: var(--color-green-800); } + .text-green-900 { + color: var(--color-green-900); + } .text-orange-800 { color: var(--color-orange-800); } @@ -1771,6 +1764,9 @@ .text-red-800 { color: var(--color-red-800); } + .text-red-900 { + color: var(--color-red-900); + } .text-rose-500 { color: var(--color-rose-500); } @@ -3260,6 +3256,11 @@ border-color: var(--color-green-500); } } + .dark\:border-green-600 { + &:where(.dark, .dark *) { + border-color: var(--color-green-600); + } + } .dark\:border-orange-300 { &:where(.dark, .dark *) { border-color: var(--color-orange-300); @@ -3280,6 +3281,11 @@ border-color: var(--color-red-500); } } + .dark\:border-red-600 { + &:where(.dark, .dark *) { + border-color: var(--color-red-600); + } + } .dark\:border-red-800 { &:where(.dark, .dark *) { border-color: var(--color-red-800); @@ -3313,6 +3319,11 @@ } } } + .dark\:bg-green-700 { + &:where(.dark, .dark *) { + background-color: var(--color-green-700); + } + } .dark\:bg-orange-400 { &:where(.dark, .dark *) { background-color: var(--color-orange-400); @@ -3333,6 +3344,11 @@ background-color: var(--color-primary-900); } } + .dark\:bg-red-700 { + &:where(.dark, .dark *) { + background-color: var(--color-red-700); + } + } .dark\:bg-red-900 { &:where(.dark, .dark *) { background-color: var(--color-red-900); @@ -3455,6 +3471,20 @@ } } } + .dark\:placeholder-green-400 { + &:where(.dark, .dark *) { + &::placeholder { + color: var(--color-green-400); + } + } + } + .dark\:placeholder-red-400 { + &:where(.dark, .dark *) { + &::placeholder { + color: var(--color-red-400); + } + } + } .dark\:ring-offset-gray-700 { &:where(.dark, .dark *) { --tw-ring-offset-color: var(--color-gray-700); @@ -3603,6 +3633,13 @@ } } } + .dark\:focus\:border-green-500 { + &:where(.dark, .dark *) { + &:focus { + border-color: var(--color-green-500); + } + } + } .dark\:focus\:border-primary-500 { &:where(.dark, .dark *) { &:focus { @@ -3610,6 +3647,13 @@ } } } + .dark\:focus\:border-red-500 { + &:where(.dark, .dark *) { + &:focus { + border-color: var(--color-red-500); + } + } + } .dark\:focus\:bg-gray-700 { &:where(.dark, .dark *) { &:focus { @@ -3645,6 +3689,13 @@ } } } + .dark\:focus\:ring-green-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-green-500); + } + } + } .dark\:focus\:ring-primary-500 { &:where(.dark, .dark *) { &:focus { @@ -3673,6 +3724,13 @@ } } } + .dark\:focus\:ring-red-500 { + &:where(.dark, .dark *) { + &:focus { + --tw-ring-color: var(--color-red-500); + } + } + } .dark\:focus\:ring-red-800 { &:where(.dark, .dark *) { &:focus { diff --git a/packages/sshecret-admin/tailwind.config.js b/packages/sshecret-admin/tailwind.config.js index 09992d0..5c1632e 100644 --- a/packages/sshecret-admin/tailwind.config.js +++ b/packages/sshecret-admin/tailwind.config.js @@ -1,6 +1,7 @@ module.exports = { content: [ "./src/sshecret_admin/templates/**/*.html", + "./src/sshecret_admin/templates/**/*.html.j2", "./src/sshecret_admin/static/**/*.js", ], safelist: [