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,7 +16,8 @@ export const handle = handleSession(
async function ({ event, resolve }) { async function ({ event, resolve }) {
let response; let response;
try { try {
let gitlabToken = event.locals.cookies.gitlabToken; if (event.locals.cookies) {
let gitlabToken = event.locals.cookies.gitlabToken || null;
let ghToken = event.locals.cookies.ghToken; let ghToken = event.locals.cookies.ghToken;
if (event.locals.cookies['kit.session']) { if (event.locals.cookies['kit.session']) {
const { permission, teamId, userId } = await getUserDetails(event, false); const { permission, teamId, userId } = await getUserDetails(event, false);
@@ -34,6 +35,7 @@ export const handle = handleSession(
event.locals.session.data = { ...newSession }; event.locals.session.data = { ...newSession };
} }
} }
}
response = await resolve(event, { response = await resolve(event, {
ssr: !event.url.pathname.startsWith('/webhooks/success') ssr: !event.url.pathname.startsWith('/webhooks/success')

View File

@@ -29,7 +29,7 @@
} }
</script> </script>
<span <div
class="relative" class="relative"
on:mouseenter={() => showActions(true)} on:mouseenter={() => showActions(true)}
on:mouseleave={() => showActions(false)} on:mouseleave={() => showActions(false)}
@@ -78,7 +78,7 @@
{/if} {/if}
{#if actionsShow} {#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"> <div class="flex space-x-2">
{#if isPasswordField} {#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}> <div on:click={() => (showPassword = !showPassword)}>
@@ -142,4 +142,4 @@
</div> </div>
</div> </div>
{/if} {/if}
</span> </div>

View File

@@ -11,7 +11,7 @@
<span class="loader" /> <span class="loader" />
</div> </div>
{:else} {: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" /> <span class="loader" />
</div> </div>
{/if} {/if}

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
import { publicPaths } from '$lib/settings'; 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)) { if (!session.userId && !publicPaths.includes(url.pathname)) {
return { return {
status: 302, status: 302,

View File

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