forms login

This commit is contained in:
Jan Prochazka
2022-11-26 11:21:37 +01:00
parent 9a5287725b
commit b1ae7d53b9
6 changed files with 84 additions and 20 deletions

View File

@@ -8,7 +8,8 @@ const AD = require('activedirectory2').promiseWrapper;
const tokenSecret = uuidv1(); const tokenSecret = uuidv1();
function shouldAuthorizeApi() { function shouldAuthorizeApi() {
return !!process.env.OAUTH_AUTH; const logins = getLogins();
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
} }
function unauthorizedResponse(req, res, text) { function unauthorizedResponse(req, res, text) {
@@ -22,7 +23,7 @@ function unauthorizedResponse(req, res, text) {
} }
function authMiddleware(req, res, next) { function authMiddleware(req, res, next) {
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', 'auth/login', '/stream']; const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
if (!shouldAuthorizeApi()) { if (!shouldAuthorizeApi()) {
return next(); return next();
@@ -85,14 +86,16 @@ module.exports = {
try { try {
const res = await ad.authenticate(login, password); const res = await ad.authenticate(login, password);
if (!res) { if (!res) {
return { error: 'login failed' }; return { error: 'Login failed' };
} }
return { return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: '1m' }), accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: '1m' }),
}; };
} catch (err) { } catch (err) {
console.log('Failed active directory authentization', err.message); console.log('Failed active directory authentization', err.message);
return { error: err.message }; return {
error: err.message,
};
} }
} }
@@ -100,7 +103,7 @@ module.exports = {
if (!logins) { if (!logins) {
return { error: 'Logins not configured' }; return { error: 'Logins not configured' };
} }
if (logins[login] == password) { if (logins.find(x => x.login == login)?.password == password) {
return { return {
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: '1m' }), accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: '1m' }),
}; };

View File

@@ -42,7 +42,7 @@ function start() {
const server = http.createServer(app); const server = http.createServer(app);
const logins = getLogins(); const logins = getLogins();
if (logins) { if (logins && process.env.BASIC_AUTH) {
app.use( app.use(
basicAuth({ basicAuth({
users: _.fromPairs(logins.map(x => [x.login, x.password])), users: _.fromPairs(logins.map(x => [x.login, x.password])),

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { internalRedirectTo } from './clientAuth';
import FormButton from './forms/FormButton.svelte'; import FormButton from './forms/FormButton.svelte';
import FormPasswordField from './forms/FormPasswordField.svelte'; import FormPasswordField from './forms/FormPasswordField.svelte';
import FormProvider from './forms/FormProvider.svelte'; import FormProvider from './forms/FormProvider.svelte';
@@ -22,15 +23,26 @@
<div class="box"> <div class="box">
<div class="heading">Log In</div> <div class="heading">Log In</div>
<FormProvider> <FormProvider>
<FormTextField label="Username" name="login" /> <FormTextField label="Username" name="login" autocomplete="username" />
<FormPasswordField label="Password" name="password" /> <FormPasswordField label="Password" name="password" autocomplete="current-password" />
<div class="submit"> <div class="submit">
<FormSubmit <FormSubmit
value="Log In" value="Log In"
on:click={e => { on:click={async e => {
enableApi(); enableApi();
apiCall('auth/login', e.detail); const resp = await apiCall('auth/login', e.detail);
if (resp.error) {
internalRedirectTo(`/?page=not-logged&error=${encodeURIComponent(resp.error)}`);
return;
}
const { accessToken } = resp;
if (accessToken) {
localStorage.setItem('accessToken', accessToken);
internalRedirectTo('/');
return;
}
internalRedirectTo(`/?page=not-logged`);
}} }}
/> />
</div> </div>

View File

@@ -1,18 +1,51 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import FormStyledButton from './buttons/FormStyledButton.svelte';
import { redirectToLogin } from './clientAuth';
onMount(() => { onMount(() => {
const removed = document.getElementById('starting_dbgate_zero'); const removed = document.getElementById('starting_dbgate_zero');
if (removed) removed.remove(); if (removed) removed.remove();
}); });
const params = new URLSearchParams(location.search);
const error = params.get('error');
function handleLogin() {
redirectToLogin(undefined, true);
}
</script> </script>
<div class='title'>Sorry, you are not authorized to run DbGate</div> <div class="root theme-light theme-type-light">
<div class="title">Sorry, you are not authorized to run DbGate</div>
{#if error}
<div class="error">{error}</div>
{/if}
<div class="button">
<FormStyledButton value="Log In" on:click={handleLogin} />
</div>
</div>
<style> <style>
.root {
color: var(--theme-font-1);
}
.title { .title {
font-size: x-large; font-size: x-large;
margin-top: 20vh; margin-top: 20vh;
text-align: center; text-align: center;
} }
.error {
margin-top: 1em;
text-align: center;
}
.button {
display: flex;
justify-content: center;
margin-top: 1em;
}
</style> </style>

View File

@@ -1,4 +1,4 @@
import { apiCall, disableApi } from './utility/api'; import { apiCall, disableApi, enableApi } from './utility/api';
import { getConfig } from './utility/metadataLoaders'; import { getConfig } from './utility/metadataLoaders';
export function isOauthCallback() { export function isOauthCallback() {
@@ -23,7 +23,7 @@ export function handleOauthCallback() {
}).then(authResp => { }).then(authResp => {
const { accessToken } = authResp; const { accessToken } = authResp;
localStorage.setItem('accessToken', accessToken); localStorage.setItem('accessToken', accessToken);
location.replace('/'); internalRedirectTo('/');
}); });
return true; return true;
@@ -42,13 +42,21 @@ export async function handleAuthOnStartup(config) {
} }
} }
export async function redirectToLogin(config = null) { export async function redirectToLogin(config = null, force = false) {
if (!config) config = await getConfig(); if (!config) {
enableApi();
config = await getConfig();
}
if (config.isLoginForm) { if (config.isLoginForm) {
const index = location.pathname.lastIndexOf('/'); if (!force) {
const loginPath = index >= 0 ? location.pathname.substring(0, index) + '/?page=login' : '/?page=login'; const params = new URLSearchParams(location.search);
location.replace(loginPath); if (params.get('page') == 'login' || params.get('page') == 'not-logged') {
return;
}
}
internalRedirectTo('/?page=login');
return;
} }
if (config.oauth) { if (config.oauth) {
@@ -60,5 +68,12 @@ export async function redirectToLogin(config = null) {
location.origin + location.pathname location.origin + location.pathname
)}&state=${encodeURIComponent(state)}` )}&state=${encodeURIComponent(state)}`
); );
return;
} }
} }
export function internalRedirectTo(path) {
const index = location.pathname.lastIndexOf('/');
const newPath = index >= 0 ? location.pathname.substring(0, index) + path : path;
location.replace(newPath);
}

View File

@@ -4,6 +4,7 @@
export let value; export let value;
export let focused = false; export let focused = false;
export let domEditor = undefined; export let domEditor = undefined;
export let autocomplete = 'new-password';
if (focused) onMount(() => domEditor.focus()); if (focused) onMount(() => domEditor.focus());
</script> </script>
@@ -17,5 +18,5 @@
on:click on:click
bind:this={domEditor} bind:this={domEditor}
on:keydown on:keydown
autocomplete="new-password" {autocomplete}
/> />