feat: Basic password reset form

This commit is contained in:
Andras Bacsai 2022-02-18 21:37:40 +01:00
parent 9db448a5e2
commit 2efca7a2b5
10 changed files with 179 additions and 24 deletions

View File

@ -16,22 +16,24 @@ export const handle = handleSession(
async function ({ event, resolve }) {
let response;
try {
let gitlabToken = event.locals.cookies.gitlabToken;
let ghToken = event.locals.cookies.ghToken;
if (event.locals.cookies['kit.session']) {
const { permission, teamId, userId } = await getUserDetails(event, false);
const newSession = {
userId,
teamId,
permission,
isAdmin: permission === 'admin' || permission === 'owner',
expires: event.locals.session.data.expires,
gitlabToken,
ghToken
};
if (event.locals.cookies) {
let gitlabToken = event.locals.cookies.gitlabToken || null;
let ghToken = event.locals.cookies.ghToken;
if (event.locals.cookies['kit.session']) {
const { permission, teamId, userId } = await getUserDetails(event, false);
const newSession = {
userId,
teamId,
permission,
isAdmin: permission === 'admin' || permission === 'owner',
expires: event.locals.session.data.expires,
gitlabToken,
ghToken
};
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
event.locals.session.data = { ...newSession };
if (JSON.stringify(event.locals.session.data) !== JSON.stringify(newSession)) {
event.locals.session.data = { ...newSession };
}
}
}

View File

@ -29,7 +29,7 @@
}
</script>
<span
<div
class="relative"
on:mouseenter={() => showActions(true)}
on:mouseleave={() => showActions(false)}
@ -78,7 +78,7 @@
{/if}
{#if actionsShow}
<div class="absolute top-0 right-0 mx-2 cursor-pointer text-warmGray-600 hover:text-white">
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
<div class="flex space-x-2">
{#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}>
@ -142,4 +142,4 @@
</div>
</div>
{/if}
</span>
</div>

View File

@ -11,7 +11,7 @@
<span class="loader" />
</div>
{:else}
<div class=" main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
<span class="loader" />
</div>
{/if}

View File

@ -6,16 +6,17 @@ import { asyncExecShell, uniqueName } from '$lib/common';
import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy';
export async function login({ email, password }) {
export async function hashPassword(password: string) {
const saltRounds = 15;
return bcrypt.hash(password, saltRounds);
}
export async function login({ email, password }) {
const users = await prisma.user.count();
const userFound = await prisma.user.findUnique({
where: { email },
include: { teams: true, permission: true },
rejectOnNotFound: false
});
console.log(userFound);
// Registration disabled if database is not seeded properly
const { isRegistrationEnabled, id } = await db.listSettings();
@ -64,7 +65,7 @@ export async function login({ email, password }) {
};
}
const hashedPassword = await bcrypt.hash(password, saltRounds);
const hashedPassword = await hashPassword(password);
if (users === 0) {
permission = 'owner';
isAdmin = true;

View File

@ -1,5 +1,7 @@
export const publicPaths = [
'/login',
'/reset',
'/reset/password',
'/webhooks/success',
'/webhooks/github',
'/webhooks/github/install',

View File

@ -2,7 +2,7 @@
import type { Load } from '@sveltejs/kit';
import { publicPaths } from '$lib/settings';
export const load: Load = async ({ fetch, url, params, session }) => {
export const load: Load = async ({ fetch, url, session }) => {
if (!session.userId && !publicPaths.includes(url.pathname)) {
return {
status: 302,

View File

@ -67,6 +67,7 @@
class:text-stone-600={loading}
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
>
<button on:click|preventDefault={() => goto('/reset')}>Reset password</button>
</div>
</form>
</div>

View File

@ -0,0 +1,26 @@
import type { RequestHandler } from '@sveltejs/kit';
import * as db from '$lib/database';
export const get: RequestHandler = async () => {
const users = await db.prisma.user.findMany({});
return {
status: 200,
body: {
users
}
};
};
export const post: RequestHandler = async (event) => {
const { secretKey } = await event.request.json();
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
return {
status: 500,
body: {
error: 'Invalid secret key.'
}
};
}
return {
status: 200
};
};

View File

@ -0,0 +1,96 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
let secretKey;
let password = false;
let users = [];
async function handleSubmit() {
try {
await post(`/reset.json`, { secretKey });
password = true;
const data = await get('/reset.json');
users = data.users;
return;
} catch ({ error }) {
return errorNotification(error);
}
}
async function resetPassword(user) {
try {
await post(`/reset/password.json`, { secretKey, user });
toast.push('Password reset done.');
return;
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<div class="icons fixed top-0 left-0 m-3 cursor-pointer" on:click={() => goto('/')}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="5" y1="12" x2="19" y2="12" />
<line x1="5" y1="12" x2="11" y2="18" />
<line x1="5" y1="12" x2="11" y2="6" />
</svg>
</div>
<div class="pb-10 pt-24 text-center text-4xl font-bold">Reset Password</div>
<div class="flex items-center justify-center">
{#if password}
<table class="mx-2 text-left">
<thead class="mb-2">
<tr>
<th class="px-2">Email</th>
<th>New password</th>
</tr>
</thead>
<tbody>
{#each users as user}
<tr>
<td class="px-2">{user.email}</td>
<td class="flex space-x-2">
<input
id="newPassword"
name="newPassword"
bind:value={user.newPassword}
placeholder="Super secure new password"
/>
<button
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100"
on:click={() => resetPassword(user)}>Reset</button
></td
>
</tr>
{/each}
</tbody>
</table>
{:else}
<form class="flex flex-col" on:submit|preventDefault={handleSubmit}>
<div class="text-center text-2xl py-2 font-bold">Secret Key</div>
<CopyPasswordField
isPasswordField={true}
id="secretKey"
name="secretKey"
bind:value={secretKey}
placeholder="You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
/>
<button type="submit" class="bg-coollabs hover:bg-coollabs-100 mx-auto w-32 my-4"
>Submit</button
>
</form>
{/if}
</div>

View File

@ -0,0 +1,27 @@
import type { RequestHandler } from '@sveltejs/kit';
import * as db from '$lib/database';
import { ErrorHandler, hashPassword } from '$lib/database';
export const post: RequestHandler = async (event) => {
const { secretKey, user } = await event.request.json();
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
return {
status: 500,
body: {
error: 'Invalid secret key.'
}
};
}
try {
const hashedPassword = await hashPassword(user.newPassword);
await db.prisma.user.update({
where: { email: user.email },
data: { password: hashedPassword }
});
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};