mirror of
https://github.com/donavon04/DocuCenter.git
synced 2025-01-18 09:40:56 -07:00
Made a bunch of changes / fixed eevrything
This commit is contained in:
parent
2776f339c6
commit
8af208a8f5
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const msal = require('@azure/msal-node');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If you have encrypted your private key with a *pass phrase* as recommended,
|
|
||||||
* you'll need to decrypt it before passing it to msal-node for initialization.
|
|
||||||
*/
|
|
||||||
// Secrets should never be hardcoded. The dotenv npm package can be used to store secrets or certificates
|
|
||||||
// in a .env file (located in project's root directory) that should be included in .gitignore to prevent
|
|
||||||
// accidental uploads of the secrets.
|
|
||||||
|
|
||||||
// Certificates can also be read-in from files via NodeJS's fs module. However, they should never be
|
|
||||||
// stored in the project's directory. Production apps should fetch certificates from
|
|
||||||
// Azure KeyVault (https://azure.microsoft.com/products/key-vault), or other secure key vaults.
|
|
||||||
|
|
||||||
// Please see "Certificates and Secrets" (https://learn.microsoft.com/azure/active-directory/develop/security-best-practices-for-app-registration#certificates-and-secrets)
|
|
||||||
// for more information.
|
|
||||||
const privateKeySource = fs.readFileSync('../certs/example.key');
|
|
||||||
|
|
||||||
const privateKeyObject = crypto.createPrivateKey({
|
|
||||||
key: privateKeySource,
|
|
||||||
passphrase: "2255", // enter your certificate passphrase here
|
|
||||||
format: 'pem'
|
|
||||||
});
|
|
||||||
|
|
||||||
const privateKey = privateKeyObject.export({
|
|
||||||
format: 'pem',
|
|
||||||
type: 'pkcs8'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Before running the sample, you will need to replace the values in the config
|
|
||||||
const config = {
|
|
||||||
auth: {
|
|
||||||
clientId: "3cdfac60-e7fb-4648-89d3-67966c497d35", //Client ID
|
|
||||||
authority: "https://login.microsoftonline.com/538b9b1c-23fa-4102-b36e-a4d83fc9c4c1", //Tenant ID
|
|
||||||
clientCertificate: {
|
|
||||||
thumbprint: 'DD79B973F2D634840948970C712907DF4423C982', // can be obtained when uploading certificate to Azure AD
|
|
||||||
privateKey: privateKey,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
system: {
|
|
||||||
loggerOptions: {
|
|
||||||
loggerCallback(loglevel, message, containsPii) {
|
|
||||||
console.log(message);
|
|
||||||
},
|
|
||||||
piiLoggingEnabled: false,
|
|
||||||
logLevel: msal.LogLevel.Verbose,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create msal application object
|
|
||||||
const cca = new msal.ConfidentialClientApplication(config);
|
|
||||||
|
|
||||||
// Create Express app
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: false }));
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
const authCodeUrlParameters = {
|
|
||||||
scopes: ["user.read"],
|
|
||||||
redirectUri: "http://localhost:3000/redirect",
|
|
||||||
responseMode: 'form_post',
|
|
||||||
};
|
|
||||||
|
|
||||||
// get url to sign user in and consent to scopes needed for application
|
|
||||||
cca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
|
|
||||||
console.log(response);
|
|
||||||
res.redirect(response);
|
|
||||||
}).catch((error) => console.log(JSON.stringify(error)));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/redirect', (req, res) => {
|
|
||||||
const tokenRequest = {
|
|
||||||
code: req.body.code,
|
|
||||||
scopes: ["user.read"],
|
|
||||||
redirectUri: "http://localhost:3000/redirect",
|
|
||||||
};
|
|
||||||
|
|
||||||
cca.acquireTokenByCode(tokenRequest).then((response) => {
|
|
||||||
console.log("\nResponse: \n:", response);
|
|
||||||
res.status(200).send('Congratulations! You have signed in successfully');
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
res.status(500).send(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const SERVER_PORT = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
app.listen(SERVER_PORT, () => {
|
|
||||||
console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`)
|
|
||||||
});
|
|
@ -1,64 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const session = require('express-session');
|
|
||||||
const passport = require('passport');
|
|
||||||
const { OIDCStrategy } = require('passport-azure-ad');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
// Session setup
|
|
||||||
app.use(
|
|
||||||
session({
|
|
||||||
secret: 'your-secret',
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Azure AD OIDC Strategy
|
|
||||||
passport.use(
|
|
||||||
new OIDCStrategy(
|
|
||||||
{
|
|
||||||
identityMetadata: `https://login.microsoftonline.com/538b9b1c-23fa-4102-b36e-a4d83fc9c4c1/v2.0/.well-known/openid-configuration`,
|
|
||||||
clientID: '3cdfac60-e7fb-4648-89d3-67966c497d35',
|
|
||||||
responseType: 'code',
|
|
||||||
responseMode: 'query',
|
|
||||||
redirectUrl: 'http://localhost:3000/auth/callback',
|
|
||||||
clientSecret: '5Gi8Q~_pmDtvN3.Jwqt85kiI.uiyAAC7Z.4iFayY',
|
|
||||||
allowHttpForRedirectUrl: true,
|
|
||||||
},
|
|
||||||
(issuer, sub, profile, accessToken, refreshToken, done) => {
|
|
||||||
// Save the user profile and tokens
|
|
||||||
return done(null, { profile, accessToken, refreshToken });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Passport serialization
|
|
||||||
passport.serializeUser((user, done) => done(null, user));
|
|
||||||
passport.deserializeUser((user, done) => done(null, user));
|
|
||||||
|
|
||||||
// Initialize Passport
|
|
||||||
app.use(passport.initialize());
|
|
||||||
app.use(passport.session());
|
|
||||||
|
|
||||||
// Authentication routes
|
|
||||||
app.get('/auth', passport.authenticate('azuread-openidconnect'));
|
|
||||||
|
|
||||||
app.get(
|
|
||||||
'/auth/callback',
|
|
||||||
passport.authenticate('azuread-openidconnect', { failureRedirect: '/' }),
|
|
||||||
(req, res) => {
|
|
||||||
res.send("Success");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Logout route
|
|
||||||
app.get('/logout', (req, res) => {
|
|
||||||
req.logout(() => {
|
|
||||||
res.redirect('/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start server
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));
|
|
@ -35,7 +35,31 @@ exports.login = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the token as an HttpOnly cookie
|
// Set the token as an HttpOnly cookie
|
||||||
res.cookie('authToken', token, {
|
res.cookie('token', token, {
|
||||||
|
httpOnly: true, // Cannot be accessed by JavaScript
|
||||||
|
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||||
|
sameSite: 'Strict', // Prevents CSRF attacks
|
||||||
|
maxAge: 3600000, // 1 hour
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the token as an HttpOnly cookie
|
||||||
|
res.cookie('name', user.name, {
|
||||||
|
httpOnly: true, // Cannot be accessed by JavaScript
|
||||||
|
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||||
|
sameSite: 'Strict', // Prevents CSRF attacks
|
||||||
|
maxAge: 3600000, // 1 hour
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the token as an HttpOnly cookie
|
||||||
|
res.cookie('email', user.email, {
|
||||||
|
httpOnly: true, // Cannot be accessed by JavaScript
|
||||||
|
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||||
|
sameSite: 'Strict', // Prevents CSRF attacks
|
||||||
|
maxAge: 3600000, // 1 hour
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the token as an HttpOnly cookie
|
||||||
|
res.cookie('role', user.role, {
|
||||||
httpOnly: true, // Cannot be accessed by JavaScript
|
httpOnly: true, // Cannot be accessed by JavaScript
|
||||||
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
|
||||||
sameSite: 'Strict', // Prevents CSRF attacks
|
sameSite: 'Strict', // Prevents CSRF attacks
|
||||||
|
@ -30,6 +30,47 @@ exports.getDocumentById = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.searchDocuments = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { query } = req.query; // Extract the search query from the query string
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
return res.status(400).json({ message: "Search query is required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the search
|
||||||
|
const documents = await Document.find({
|
||||||
|
$or: [
|
||||||
|
{ title: { $regex: query, $options: "i" } }, // Case-insensitive match in title
|
||||||
|
{ body: { $regex: query, $options: "i" } }, // Case-insensitive match in body
|
||||||
|
{ tags: { $regex: query, $options: "i" } }, // Case-insensitive match in tags array
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight the query in the fields
|
||||||
|
const highlightedDocuments = documents.map((doc) => {
|
||||||
|
const highlightMatch = (text) =>
|
||||||
|
text.replace(
|
||||||
|
new RegExp(`(${query})`, "gi"), // Match the query case-insensitively
|
||||||
|
'<span style="background: yellow;">$1</span>'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...doc._doc, // Convert Mongoose document to plain object
|
||||||
|
title: highlightMatch(doc.title || ""),
|
||||||
|
body: highlightMatch(doc.body || ""),
|
||||||
|
tags: doc.tags.map((tag) => highlightMatch(tag)), // Highlight each tag
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(highlightedDocuments); // Return the matching and highlighted documents
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.updateDocumentById = async (req, res) => {
|
exports.updateDocumentById = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const documentId = req.params.id;
|
const documentId = req.params.id;
|
||||||
|
@ -13,7 +13,7 @@ const DocumentSchema = new mongoose.Schema(
|
|||||||
visibility: {
|
visibility: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
default: "Public",
|
default: "public",
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
type: [String], //Array of strings
|
type: [String], //Array of strings
|
||||||
|
@ -21,7 +21,7 @@ const UserSchema = new mongoose.Schema(
|
|||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false, // Optional for OAuth users
|
required: false, // Optional for OAuth users
|
||||||
default: "User", //User = Read, Write Permissions | Admin = Read, Write, Delete Permissions
|
default: "user", //User = Read, Write Permissions | Admin = Read, Write, Delete Permissions
|
||||||
},
|
},
|
||||||
oauthProviders: [
|
oauthProviders: [
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@ const router = express.Router();
|
|||||||
const documentController = require('../controllers/documentController');
|
const documentController = require('../controllers/documentController');
|
||||||
|
|
||||||
router.post('/', documentController.createDocument); //Create
|
router.post('/', documentController.createDocument); //Create
|
||||||
|
router.get('/search', documentController.searchDocuments); //Get a document by id
|
||||||
router.get('/', documentController.getDocument); //Get all documents
|
router.get('/', documentController.getDocument); //Get all documents
|
||||||
router.get('/:id', documentController.getDocumentById); //Get a document by id
|
router.get('/:id', documentController.getDocumentById); //Get a document by id
|
||||||
router.put('/:id', authenticateToken, documentController.updateDocumentById); //Update a document by id
|
router.put('/:id', authenticateToken, documentController.updateDocumentById); //Update a document by id
|
||||||
|
@ -1 +1 @@
|
|||||||
BACKEND_URI='http://localhost:3000'
|
PUBLIC_BASE_URL='http://localhost:3000'
|
20
frontend/src/hooks.server.ts
Normal file
20
frontend/src/hooks.server.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export async function handle({ event, resolve }) {
|
||||||
|
// this cookie would be set inside a login route
|
||||||
|
const token = event.cookies.get('token');
|
||||||
|
const name = event.cookies.get('name');
|
||||||
|
const email = event.cookies.get('email');
|
||||||
|
const role = event.cookies.get('role');
|
||||||
|
|
||||||
|
// you can get the user data from a database
|
||||||
|
// const user = await getUser(session)
|
||||||
|
|
||||||
|
// this is passed to `event` inside server `load` functions
|
||||||
|
// and passed to handlers inside `+page.ts`
|
||||||
|
event.locals.user = {
|
||||||
|
token: token,
|
||||||
|
name: name,
|
||||||
|
email: email,
|
||||||
|
role: role
|
||||||
|
}
|
||||||
|
return resolve(event)
|
||||||
|
}
|
@ -1,42 +1,3 @@
|
|||||||
import type { LayoutServerLoad } from './$types';
|
export async function load({ locals }) {
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
return { user: locals.user }
|
||||||
|
}
|
||||||
export const load: LayoutServerLoad = async ({ cookies, fetch }) => {
|
|
||||||
let isAuthenticated = false;
|
|
||||||
let user = null;
|
|
||||||
let errorMessage = '';
|
|
||||||
|
|
||||||
// Retrieve the auth token from cookies
|
|
||||||
const token = cookies.get('authToken');
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
errorMessage = 'No auth token found. Please log in.';
|
|
||||||
return { isAuthenticated, user, errorMessage };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Make a server-side fetch request to validate the token
|
|
||||||
const response = await fetch(`${PUBLIC_BASE_URL}/auth/status`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
isAuthenticated = data.authenticated;
|
|
||||||
user = data.user;
|
|
||||||
} else {
|
|
||||||
errorMessage = 'Authentication failed. Please log in again.';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking authentication:', error);
|
|
||||||
errorMessage = 'An error occurred while checking authentication.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return data to the layout
|
|
||||||
return { isAuthenticated, user, errorMessage };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@ -1,32 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
|
||||||
import '../app.postcss';
|
import '../app.postcss';
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import { isAuthenticated } from '$lib/stores/auth';
|
|
||||||
|
|
||||||
let dropDown = false; // For the dropdown menu (login menu)
|
let dropDown = false; // For the dropdown menu (login menu)
|
||||||
|
|
||||||
export let data: {
|
function getInitials(name) {
|
||||||
isAuthenticated: boolean;
|
return name
|
||||||
errorMessage: string;
|
.split(" ") // Split the name into words
|
||||||
|
.slice(0, 2) // Take the first two words
|
||||||
|
.map(word => word.charAt(0).toUpperCase()) // Get the first letter of each word and capitalize
|
||||||
|
.join(""); // Join them into a string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the store values when this layout is loaded
|
|
||||||
$: isAuthenticated.set(data.isAuthenticated); //This is a store (MAGIC)
|
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
|
||||||
const response = await fetch('/signout', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
data.isAuthenticated = false;
|
|
||||||
} else {
|
|
||||||
console.error('Failed to sign out.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
||||||
@ -35,17 +20,17 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="relative dropdown-container">
|
<div class="relative dropdown-container">
|
||||||
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
<button class="rounded-3xl bg-blue-500 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
||||||
on:click={() => dropDown = !dropDown}>
|
on:click={() => dropDown = !dropDown}>
|
||||||
{#if data.isAuthenticated}
|
{#if $page.data.user.name}
|
||||||
<img
|
<div class="rounded-full text-white font-bold bg-blue-400 p-2">
|
||||||
src="https://www.citypng.com/public/uploads/preview/hd-mcdonalds-red-round-circular-circle-logo-icon-png-image-7017516947898359qtpcakiqi.png"
|
{getInitials($page.data.user.name)}
|
||||||
class="avatar rounded-full size-8"
|
</div>
|
||||||
alt="User Avatar"
|
|
||||||
/>
|
|
||||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||||
{:else}
|
{:else}
|
||||||
|
<div class="rounded-full text-white font-bold bg-blue-400 p-2 px-3">
|
||||||
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
|
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
|
||||||
|
</div>
|
||||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
@ -53,17 +38,16 @@
|
|||||||
{#if dropDown}
|
{#if dropDown}
|
||||||
<!-- Dropdown menu -->
|
<!-- Dropdown menu -->
|
||||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
||||||
{#if data.isAuthenticated}
|
{#if $page.data.user.name}
|
||||||
<span class="signedInText block mb-2 text-sm">
|
<span class="signedInText block mb-2 text-sm">
|
||||||
<small>Signed in</small><br/>
|
<small>Signed in as</small><br/>
|
||||||
|
<b>{$page.data.user.name}</b>
|
||||||
</span>
|
</span>
|
||||||
<button
|
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
<form action="/logout" method="POST">
|
||||||
on:click={() => {dropDown = false; handleSignOut();}}>
|
<button class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full">Sign Out</button>
|
||||||
Sign Out
|
</form>
|
||||||
</button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<span class="notSignedInText block mb-2">You are not signed in</span>
|
|
||||||
<a href="/login" on:click={() => {dropDown = false;}}>
|
<a href="/login" on:click={() => {dropDown = false;}}>
|
||||||
<button
|
<button
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import '../app.postcss';
|
|
||||||
import { signIn, signOut } from "@auth/sveltekit/client"
|
|
||||||
import { page } from "$app/stores"
|
|
||||||
|
|
||||||
let dropDown = false;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
|
|
||||||
<a href="/home">
|
|
||||||
<img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="relative dropdown-container">
|
|
||||||
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
|
|
||||||
on:click={() => dropDown = !dropDown}>
|
|
||||||
{#if $page.data.session}
|
|
||||||
{#if $page.data.session.user?.image}
|
|
||||||
<img
|
|
||||||
src={$page.data.session.user.image}
|
|
||||||
class="avatar rounded-full size-8"
|
|
||||||
alt="User Avatar"
|
|
||||||
/>
|
|
||||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
|
|
||||||
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if dropDown}
|
|
||||||
<!-- Dropdown menu -->
|
|
||||||
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
|
|
||||||
{#if $page.data.session}
|
|
||||||
<span class="signedInText block mb-2 text-sm">
|
|
||||||
<small>Signed in as</small><br/>
|
|
||||||
<strong>{$page.data.session.user?.name ?? "User"}</strong>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
|
||||||
on:click={() => { signOut(); dropDown = false;}}>
|
|
||||||
Sign Out
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<span class="notSignedInText block mb-2">You are not signed in</span>
|
|
||||||
<button
|
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
|
|
||||||
on:click={() => { signIn('microsoft-entra-id'); dropDown = false;}}>
|
|
||||||
Sign In
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<slot />
|
|
@ -13,9 +13,9 @@
|
|||||||
let title = "";
|
let title = "";
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
const url = PUBLIC_BASE_URL+"/document";
|
const url = `${PUBLIC_BASE_URL}/api/document`;
|
||||||
|
const token = $page.data.user?.token;
|
||||||
let created_by = $page.data.session.user?.name ?? "User";
|
let created_by = $page.data.user?.name ?? "User";
|
||||||
|
|
||||||
// Get the HTML from the tiptap editor
|
// Get the HTML from the tiptap editor
|
||||||
if (editorRef) {
|
if (editorRef) {
|
||||||
@ -39,7 +39,8 @@
|
|||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { page } from "$app/stores"
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||||
import { isAuthenticated } from '$lib/stores/auth';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Make the GET request to your API
|
// Make the GET request to your API
|
||||||
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
|
const response = await fetch(`${PUBLIC_BASE_URL}/api/document/${id}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
document = await response.json(); // Store document data
|
document = await response.json(); // Store document data
|
||||||
} else {
|
} else {
|
||||||
@ -36,7 +36,8 @@
|
|||||||
let agreeToDelete = false;
|
let agreeToDelete = false;
|
||||||
// Function to delete a document by ID
|
// Function to delete a document by ID
|
||||||
async function deleteDocument(id) {
|
async function deleteDocument(id) {
|
||||||
const url = PUBLIC_BASE_URL+`/documents/${id}`; // Update with your API base URL if different
|
const url = `${PUBLIC_BASE_URL}/api/document/${id}`; // Update with your API base URL if different
|
||||||
|
const token = $page.data.user?.token;
|
||||||
|
|
||||||
// Show the confirmation popup
|
// Show the confirmation popup
|
||||||
alertPopup = true;
|
alertPopup = true;
|
||||||
@ -60,6 +61,7 @@ async function deleteDocument(id) {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,7 +127,7 @@ async function deleteDocument(id) {
|
|||||||
<div class="flex flex-col justify-between">
|
<div class="flex flex-col justify-between">
|
||||||
<div class="w-full flex flex-row justify-between">
|
<div class="w-full flex flex-row justify-between">
|
||||||
<h1 class="text-2xl">{document.title}</h1>
|
<h1 class="text-2xl">{document.title}</h1>
|
||||||
{#if $isAuthenticated}
|
{#if $page.data.user.name}
|
||||||
<div>
|
<div>
|
||||||
<a href="/edit?id={id}"><button class="bg-yellow-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto"><i class="fa-solid fa-pencil fa-lg"></i></button></a>
|
<a href="/edit?id={id}"><button class="bg-yellow-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto"><i class="fa-solid fa-pencil fa-lg"></i></button></a>
|
||||||
<button class="bg-red-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto" on:click={() => { deleteDocument(id)}}><i class="fa-solid fa-trash fa-lg"></i></button>
|
<button class="bg-red-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto" on:click={() => { deleteDocument(id)}}><i class="fa-solid fa-trash fa-lg"></i></button>
|
||||||
@ -150,10 +152,10 @@ async function deleteDocument(id) {
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="">
|
||||||
{#if document.created_at}
|
{#if document.createdAt}
|
||||||
<p class="font-semibold">Created: {new Date(document.created_at).toLocaleString()}</p>
|
<p class="font-semibold">Created: {new Date(document.createdAt).toLocaleString()}</p>
|
||||||
{#if document.updated_at}
|
{#if document.edited_by}
|
||||||
<p class="font-semibold">Updated: {new Date(document.updated_at).toLocaleString()}</p>
|
<p class="font-semibold">Updated: {new Date(document.updatedAt).toLocaleString()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,14 +14,12 @@
|
|||||||
let title = "";
|
let title = "";
|
||||||
|
|
||||||
let document = null; // To store fetched document data
|
let document = null; // To store fetched document data
|
||||||
let error = null; // To store any error that occurs during fetch
|
|
||||||
let documentId;
|
|
||||||
let tags = [];
|
let tags = [];
|
||||||
|
|
||||||
// Function to fetch the document by ID
|
// Function to fetch the document by ID
|
||||||
async function fetchDocumentById(id) {
|
async function fetchDocumentById(id) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
|
const response = await fetch(`${PUBLIC_BASE_URL}/api/document/${id}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const fetchedDocument = await response.json();
|
const fetchedDocument = await response.json();
|
||||||
document = fetchedDocument; // Update document data
|
document = fetchedDocument; // Update document data
|
||||||
@ -41,7 +39,6 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||||
documentId = id;
|
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
error = 'ID is required';
|
error = 'ID is required';
|
||||||
@ -57,11 +54,11 @@
|
|||||||
async function save() {
|
async function save() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const id = urlParams.get('id'); // Get the `id` from the query string
|
const id = urlParams.get('id'); // Get the `id` from the query string
|
||||||
const url = PUBLIC_BASE_URL+"/document/"+id;
|
const url = `${PUBLIC_BASE_URL}/api/document/`+id;
|
||||||
|
|
||||||
console.log("Title value:", title);
|
console.log("Title value:", title);
|
||||||
|
|
||||||
let edited_by = $page.data.session.user?.name ?? "User";
|
const edited_by = $page.data.user?.name;
|
||||||
|
const token = $page.data.user?.token;
|
||||||
|
|
||||||
// Get the HTML from the tiptap editor
|
// Get the HTML from the tiptap editor
|
||||||
if (editorRef) {
|
if (editorRef) {
|
||||||
@ -86,7 +83,8 @@
|
|||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from "$app/stores"
|
import { page } from "$app/stores"
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||||
import { isAuthenticated } from '$lib/stores/auth';
|
|
||||||
|
|
||||||
let query = false;
|
let query = false;
|
||||||
let searchQuery = "";
|
let searchQuery = "";
|
||||||
@ -25,7 +24,7 @@
|
|||||||
// Function to simulate search API call
|
// Function to simulate search API call
|
||||||
async function searchDocuments(query) {
|
async function searchDocuments(query) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(PUBLIC_BASE_URL+`/search?query=${query}`);
|
const res = await fetch(`${PUBLIC_BASE_URL}/api/document/search?query=${query}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
@ -42,7 +41,7 @@
|
|||||||
// Function to fetch all documents from the API
|
// Function to fetch all documents from the API
|
||||||
async function getAllDocuments() {
|
async function getAllDocuments() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(PUBLIC_BASE_URL+`/document`);
|
const res = await fetch(`${PUBLIC_BASE_URL}/api/document`);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`Failed to fetch documents: ${res.statusText}`);
|
throw new Error(`Failed to fetch documents: ${res.statusText}`);
|
||||||
@ -81,7 +80,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="flex items-center justify-center h-full flex-col">
|
<div class="flex items-center justify-center h-full flex-col">
|
||||||
{#if $isAuthenticated}
|
{#if $page.data.user.role == "admin"}
|
||||||
<div class="flex flex-row justify-end w-full">
|
<div class="flex flex-row justify-end w-full">
|
||||||
<a href="/add" class="bg-green-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 mr-4 mt-3 text-white font-roboto">
|
<a href="/add" class="bg-green-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 mr-4 mt-3 text-white font-roboto">
|
||||||
<i class="fa-solid fa-folder-plus fa-lg"></i>
|
<i class="fa-solid fa-folder-plus fa-lg"></i>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||||
|
|
||||||
async function handleSignIn(event) {
|
async function handleSignIn(event) {
|
||||||
event.preventDefault(); // Prevent the default form submission
|
event.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
@ -11,7 +10,7 @@
|
|||||||
const data = Object.fromEntries(formData.entries());
|
const data = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${PUBLIC_BASE_URL}/auth/login`, {
|
const response = await fetch(`${PUBLIC_BASE_URL}/api/auth/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
27
frontend/src/routes/logout/+page.server.ts
Normal file
27
frontend/src/routes/logout/+page.server.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
export const load = async () => {
|
||||||
|
// we only use this endpoint for the api
|
||||||
|
// and don't need to see the page
|
||||||
|
redirect(302, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default({ cookies }) {
|
||||||
|
cookies.set('token', '', {
|
||||||
|
path: '/',
|
||||||
|
expires: new Date(0),
|
||||||
|
})
|
||||||
|
cookies.set('name', '', {
|
||||||
|
path: '/',
|
||||||
|
expires: new Date(0),
|
||||||
|
})
|
||||||
|
cookies.set('email', '', {
|
||||||
|
path: '/',
|
||||||
|
expires: new Date(0),
|
||||||
|
})
|
||||||
|
|
||||||
|
// redirect the user
|
||||||
|
redirect(302, '/login')
|
||||||
|
},
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
import { signIn } from "../../auth"
|
|
||||||
import type { Actions } from "./$types"
|
|
||||||
export const actions: Actions = { default: signIn }
|
|
@ -1,12 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ cookies }) => {
|
|
||||||
try {
|
|
||||||
// Clear the authToken cookie
|
|
||||||
cookies.delete('authToken', { path: '/' });
|
|
||||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error during sign out:', error);
|
|
||||||
return new Response(JSON.stringify({ success: false, message: 'Failed to sign out.' }), { status: 500 });
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { PUBLIC_BASE_URL } from '$env/static/public';
|
import { PUBLIC_BASE_URL } from '$env/static/public';
|
||||||
|
|
||||||
async function handleSignup(event) {
|
async function handleSignup(event) {
|
||||||
event.preventDefault(); // Prevent the default form submission
|
event.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
@ -11,7 +10,7 @@
|
|||||||
const data = Object.fromEntries(formData.entries());
|
const data = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${PUBLIC_BASE_URL}/user`, {
|
const response = await fetch(`${PUBLIC_BASE_URL}/api/user`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Loading…
Reference in New Issue
Block a user