feat: Basic password reset form
This commit is contained in:
parent
9db448a5e2
commit
2efca7a2b5
32
src/hooks.ts
32
src/hooks.ts
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,7 @@
|
||||
export const publicPaths = [
|
||||
'/login',
|
||||
'/reset',
|
||||
'/reset/password',
|
||||
'/webhooks/success',
|
||||
'/webhooks/github',
|
||||
'/webhooks/github/install',
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
26
src/routes/reset/index.json.ts
Normal file
26
src/routes/reset/index.json.ts
Normal 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
|
||||
};
|
||||
};
|
96
src/routes/reset/index.svelte
Normal file
96
src/routes/reset/index.svelte
Normal 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>
|
27
src/routes/reset/password.json.ts
Normal file
27
src/routes/reset/password.json.ts
Normal 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);
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user