Basic login

This commit is contained in:
Douglas Barone 2023-06-27 15:52:47 -04:00
parent 3a9f46b85e
commit 2ead863ace
13 changed files with 568 additions and 760 deletions

1120
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,10 +37,12 @@
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^4.15.0", "@prisma/client": "^4.15.0",
"@types/cors": "^2.8.13",
"@types/netmask": "^2.0.1", "@types/netmask": "^2.0.1",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bree": "^9.1.3", "bree": "^9.1.3",
"concurrently": "^8.2.0", "concurrently": "^8.2.0",
"cors": "^2.8.5",
"dotenv": "^16.1.4", "dotenv": "^16.1.4",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",

View File

@ -1,5 +1,6 @@
import { Request, Response, Router } from 'express' import { Request, Response, Router } from 'express'
import { AuthenticationService } from '../services/AuthenticationService.js' import { AuthenticationService } from '../services/AuthenticationService.js'
import { InvalidCredentialsError } from 'ldapts'
const router = Router() const router = Router()
@ -8,7 +9,7 @@ class LoginController {
const { username, password } = req.body const { username, password } = req.body
if (!username || !password) { if (!username || !password) {
res.status(400).json({ error: 'Missing username or password' }) res.status(400).json({ error: 'Usuário e senha devem ser informados!' })
return return
} }
@ -16,6 +17,10 @@ class LoginController {
const token = await AuthenticationService.login(username, password) const token = await AuthenticationService.login(username, password)
res.json({ token }) res.json({ token })
} catch (error: any) { } catch (error: any) {
if (error instanceof InvalidCredentialsError) {
res.status(401).json({ error: 'Usuário ou senha inválidos' })
return
}
res.status(401).json({ error: error.message }) res.status(401).json({ error: error.message })
} }
} }

View File

@ -1,5 +1,6 @@
import express, { Request, Response } from 'express' import express, { Request, Response } from 'express'
import bodyParser from 'body-parser' import bodyParser from 'body-parser'
import cors from 'cors'
import { populateUserMiddleware } from './middlewares/populateUserMiddleware.js' import { populateUserMiddleware } from './middlewares/populateUserMiddleware.js'
import { authMiddleware } from './middlewares/authMiddleware.js' import { authMiddleware } from './middlewares/authMiddleware.js'
@ -12,6 +13,7 @@ import PrinterDiscoveryRouter from './controllers/PrinterDiscoveryController.js'
export const app = express() export const app = express()
app.use(cors())
app.use(loggerMiddleware) app.use(loggerMiddleware)
app.use('/api', bodyParser.json()) app.use('/api', bodyParser.json())

1
web/.env.dev Normal file
View File

@ -0,0 +1 @@

0
web/.env.prod Normal file
View File

27
web/src/api.ts Normal file
View File

@ -0,0 +1,27 @@
const BASE_URL = process.env.BASE_URL || "http://localhost:8000/api/";
export async function api(endpoint: string, options: any) {
const token = localStorage.getItem("token");
if (token) {
options.headers = {
...options.headers,
Authorization: token,
};
}
options.headers = {
...options.headers,
"Content-Type": "application/json",
};
const response = await fetch(BASE_URL + endpoint, options);
const json = await response.json();
if (response.ok) {
return json;
}
throw new Error(json.error);
}

13
web/src/auth.ts Normal file
View File

@ -0,0 +1,13 @@
const LOCAL_STORAGE_KEY = "token";
export function saveJwtToken(token: string) {
localStorage.setItem(LOCAL_STORAGE_KEY, token);
}
export function getJwtToken() {
return localStorage.getItem(LOCAL_STORAGE_KEY);
}
export function removeJwtToken() {
localStorage.removeItem(LOCAL_STORAGE_KEY);
}

View File

@ -0,0 +1,9 @@
<template>
<v-app>
<default-view />
</v-app>
</template>
<script lang="ts" setup>
import DefaultView from './View.vue'
</script>

View File

@ -0,0 +1,11 @@
<template>
<v-app id="inspire">
<v-main> <router-view /> </v-main>
</v-app>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const drawer = ref(true);
</script>

View File

@ -2,6 +2,18 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
const routes = [ const routes = [
{
path: "/login",
component: () => import("@/layouts/simple/Default.vue"),
children: [
{
path: "",
name: "Login",
component: () =>
import(/* webpackChunkName: "login" */ "@/views/Login.vue"),
},
],
},
{ {
path: "/", path: "/",
component: () => import("@/layouts/default/Default.vue"), component: () => import("@/layouts/default/Default.vue"),

View File

@ -1,5 +1,23 @@
<template> <template>
<div><h1>Home</h1></div> <div>
<h1>Home</h1>
{{ printers }}
</div>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import { api } from "@/api";
import { ref } from "vue";
const printers = ref([]);
async function getPrinters() {
const data = await api("printer", {
method: "GET",
});
printers.value = data;
}
getPrinters();
</script>

102
web/src/views/Login.vue Normal file
View File

@ -0,0 +1,102 @@
<template>
<v-container class="fill-height">
<v-form
:disabled="loading"
style="width: 480px"
class="mx-auto"
@submit.prevent="login"
v-model="valid"
>
<v-card :loading="loading">
<v-card-title class="mb-2 font-weight-regular"> Login </v-card-title>
<v-card-text>
<v-text-field
placeholder="SIAPE"
variant="outlined"
label="Usuário"
color="primary"
v-model="username"
autocomplete="username"
required
/>
<v-text-field
placeholder="Senha do SUAP"
type="password"
variant="outlined"
label="Senha"
color="primary"
v-model="password"
autocomplete="current-password"
required
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
type="submit"
color="primary"
size="large"
:disabled="loading || !valid"
>
Entrar
</v-btn>
</v-card-actions>
</v-card>
<div v-if="errors.length">
<v-alert
v-for="error in errors"
icon="mdi-alert-circle"
:key="error"
color="error"
class="mt-2"
variant="outlined"
closable
>
{{ error }}
</v-alert>
</div>
</v-form>
</v-container>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { api } from "@/api";
import { saveJwtToken } from "@/auth";
import { useRouter } from "vue-router";
const username = ref<string>("");
const password = ref<string>("");
const valid = ref(false);
const errors = reactive<string[]>([]);
const loading = ref(false);
const router = useRouter();
async function login() {
errors.splice(0, errors.length);
try {
loading.value = true;
const data = await api("login", {
method: "POST",
body: JSON.stringify({
username: username.value,
password: password.value,
}),
});
const token = data.token;
saveJwtToken(token);
router.push({ name: "Home" });
} catch (error: any) {
errors.push(error.message);
} finally {
loading.value = false;
}
}
</script>