Add web interface with admin functionality (#167)

This commit is contained in:
Alex
2022-05-21 02:23:55 -05:00
committed by GitHub
parent 79dea504b0
commit a779c3803c
76 changed files with 8252 additions and 87 deletions

View File

@@ -0,0 +1,12 @@
import type { RequestHandler } from '@sveltejs/kit';
import { getRequest } from '../../../../lib/api';
export const get: RequestHandler = async ({ request, locals }) => {
const allUsers = await getRequest('user?isAll=true', locals.user!.accessToken)
return {
status: 200,
body: { allUsers }
};
}

View File

@@ -0,0 +1,52 @@
import type { RequestHandler } from '@sveltejs/kit';
import { putRequest } from '$lib/api';
import * as cookie from 'cookie';
export const post: RequestHandler = async ({ request, locals }) => {
const { id, isAdmin } = await request.json()
const res = await putRequest('user', {
id,
isAdmin,
}, locals.user!.accessToken);
if (res.statusCode) {
return {
status: res.statusCode,
body: JSON.stringify(res)
}
}
if (res.id == locals.user!.id) {
return {
status: 200,
body: { userInfo: res },
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: res.id,
accessToken: locals.user!.accessToken,
firstName: res.firstName,
lastName: res.lastName,
isAdmin: res.isAdmin,
email: res.email,
}), {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30,
})
}
}
} else {
return {
status: 200,
body: { userInfo: { ...locals.user! } },
}
}
}

View File

@@ -0,0 +1,49 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { fade } from 'svelte/transition';
import LoginForm from '$lib/components/forms/login-form.svelte';
import UpdateForm from '../../../lib/components/forms/update-form.svelte';
import SelectAdminForm from '../../../lib/components/forms/select-admin-form.svelte';
let shouldShowUpdateForm = false;
let shouldShowSelectAdminForm = false;
const onLoginSuccess = async () => {
goto('/photos');
};
const onNeedUpdate = () => {
shouldShowUpdateForm = true;
shouldShowSelectAdminForm = false;
};
const onNeedSelectAdmin = () => {
shouldShowUpdateForm = false;
shouldShowSelectAdminForm = true;
};
</script>
<svelte:head>
<title>Immich - Login</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
{#if !shouldShowUpdateForm && !shouldShowSelectAdminForm}
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
<LoginForm on:success={onLoginSuccess} on:need-update={onNeedUpdate} on:need-select-admin={onNeedSelectAdmin} />
</div>
{/if}
{#if shouldShowUpdateForm}
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
<UpdateForm on:success={onLoginSuccess} />
</div>
{/if}
{#if shouldShowSelectAdminForm}
<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
<SelectAdminForm on:success={onLoginSuccess} />
</div>
{/if}
</section>

View File

@@ -0,0 +1,229 @@
import type { RequestHandler } from '@sveltejs/kit';
import { serverEndpoint } from '$lib/constants';
import * as cookie from 'cookie'
import { getRequest, putRequest } from '$lib/api';
type LoggedInUser = {
accessToken: string;
userId: string;
userEmail: string;
firstName: string;
lastName: string;
isAdmin: boolean;
}
export const post: RequestHandler = async ({ request }) => {
const form = await request.formData();
const email = form.get('email')
const password = form.get('password')
const payload = {
email,
password,
}
const res = await fetch(`${serverEndpoint}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
})
if (res.status === 201) {
// Login success
const loggedInUser = await res.json() as LoggedInUser;
/**
* Support legacy users with two scenario
*
* Scenario 1 - If one user exists on the server - make the user admin and ask for name.
* Scenario 2 - After assigned as admin, scenario 1 user not complete update form with names
* Scenario 3 - If two users exists on the server and no admin - ask to choose which one will be made admin
*/
// check how many user on the server
const { userCount } = await getRequest('user/count', '');
const { userCount: adminUserCount } = await getRequest('user/count?isAdmin=true', '')
/**
* Scenario 1 handler
*/
if (userCount == 1 && !loggedInUser.isAdmin) {
const updatedUser = await putRequest('user', {
id: loggedInUser.userId,
isAdmin: true
}, loggedInUser.accessToken)
/**
* Scenario 2 handler for current admin user
*/
let bodyResponse = { success: true, needUpdate: false }
if (loggedInUser.firstName == "" || loggedInUser.lastName == "") {
bodyResponse = { success: false, needUpdate: true }
}
return {
status: 200,
body: {
...bodyResponse,
user: {
id: updatedUser.userId,
accessToken: loggedInUser.accessToken,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
isAdmin: updatedUser.isAdmin,
email: updatedUser.email,
},
},
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: updatedUser.userId,
accessToken: loggedInUser.accessToken,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
isAdmin: updatedUser.isAdmin,
email: updatedUser.email,
}), {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30,
})
}
}
}
/**
* Scenario 3 handler
*/
if (userCount >= 2 && adminUserCount == 0) {
return {
status: 200,
body: {
needSelectAdmin: true,
user: {
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail
},
success: 'success'
},
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail
}), {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30,
})
}
}
}
/**
* Scenario 2 handler
*/
if (loggedInUser.firstName == "" || loggedInUser.lastName == "") {
return {
status: 200,
body: {
needUpdate: true,
user: {
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail
},
},
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail
}), {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30,
})
}
}
}
return {
status: 200,
body: {
user: {
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail
},
success: 'success'
},
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: loggedInUser.userId,
accessToken: loggedInUser.accessToken,
firstName: loggedInUser.firstName,
lastName: loggedInUser.lastName,
isAdmin: loggedInUser.isAdmin,
email: loggedInUser.userEmail,
}), {
// send cookie for every page
path: '/',
// server side only cookie so you can't use `document.cookie`
httpOnly: true,
// only requests from same site can send cookies
// and serves to protect from CSRF
// https://developer.mozilla.org/en-US/docs/Glossary/CSRF
sameSite: 'strict',
// set cookie to expire after a month
maxAge: 60 * 60 * 24 * 30,
})
}
}
} else {
return {
status: 400,
body: {
error: 'Incorrect email or password'
}
}
}
}

View File

@@ -0,0 +1,63 @@
import type { RequestHandler } from '@sveltejs/kit';
import { putRequest } from '../../../lib/api';
import * as cookie from 'cookie'
export const post: RequestHandler = async ({ request, locals }) => {
const form = await request.formData();
const firstName = form.get('firstName')
const lastName = form.get('lastName')
if (locals.user) {
const updatedUser = await putRequest('user', {
id: locals.user.id,
firstName,
lastName
}, locals.user.accessToken)
return {
status: 200,
body: {
user: {
id: updatedUser.id,
accessToken: locals.user.accessToken,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
isAdmin: updatedUser.isAdmin,
email: updatedUser.email,
},
success: 'Update user success'
},
headers: {
'Set-Cookie': cookie.serialize('session', JSON.stringify(
{
id: updatedUser.id,
accessToken: locals.user.accessToken,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
isAdmin: updatedUser.isAdmin,
email: updatedUser.email,
}), {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 30,
})
}
}
}
return {
status: 400,
body: {
error: 'Cannot get access token from cookies'
}
}
}

View File

@@ -0,0 +1,39 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
import { serverEndpoint } from '$lib/constants';
export const load: Load = async ({ session, fetch }) => {
const res = await fetch(`${serverEndpoint}/user/count`);
const { userCount } = await res.json();
if (userCount != 0) {
// Admin has been registered, redirect to login
if (!session.user) {
return {
status: 302,
redirect: '/auth/login',
};
} else {
return {
status: 302,
redirect: '/dashboard',
};
}
}
return {};
};
</script>
<script lang="ts">
import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
</script>
<svelte:head>
<title>Immich - Admin Registration</title>
</svelte:head>
<section class="h-screen w-screen flex place-items-center place-content-center">
<AdminRegistrationForm />
</section>

View File

@@ -0,0 +1,43 @@
import type { RequestHandler } from '@sveltejs/kit';
import { serverEndpoint } from '$lib/constants';
export const post: RequestHandler = async ({ request }) => {
const form = await request.formData();
const email = form.get('email')
const password = form.get('password')
const firstName = form.get('firstName')
const lastName = form.get('lastName')
const payload = {
email,
password,
firstName,
lastName,
}
const res = await fetch(`${serverEndpoint}/auth/admin-sign-up`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
})
if (res.status === 201) {
return {
status: 201,
body: {
success: 'Succesfully create admin account'
}
}
} else {
return {
status: 400,
body: {
error: await res.json()
}
}
}
}