Check user auth in api request
This commit is contained in:
21
backend/middleware/auth.js
Normal file
21
backend/middleware/auth.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { defaultDangerKey } = require('../routes/auth/authSettings');
|
||||
|
||||
function verifySinedIn(req, res, next) {
|
||||
const token = req.header('Authorization');
|
||||
const secretKey = process.env.SECRET_KEY || defaultDangerKey;
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Access denied' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, secretKey);
|
||||
req.userId = decoded.userId;
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifySinedIn };
|
||||
4
backend/routes/auth/authSettings.js
Normal file
4
backend/routes/auth/authSettings.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const roles = [{ uid: 'admin' }, { uid: 'moderator' }, { uid: 'user' }];
|
||||
const defaultDangerKey = 'test-plat-default-key';
|
||||
|
||||
module.exports = { roles, defaultDangerKey };
|
||||
@@ -1,2 +0,0 @@
|
||||
const roles = [{ uid: 'admin' }, { uid: 'moderator' }, { uid: 'user' }];
|
||||
module.exports = roles;
|
||||
@@ -4,9 +4,11 @@ const defineUser = require('../../models/users');
|
||||
const { DataTypes } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { defaultDangerKey } = require('./authSettings');
|
||||
|
||||
module.exports = function (sequelize) {
|
||||
const User = defineUser(sequelize, DataTypes);
|
||||
const secretKey = process.env.SECRET_KEY || defaultDangerKey;
|
||||
|
||||
router.post('/signin', async (req, res) => {
|
||||
try {
|
||||
@@ -24,7 +26,7 @@ module.exports = function (sequelize) {
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ error: 'Authentication failed' });
|
||||
}
|
||||
const accessToken = jwt.sign({ userId: user.id }, 'your-secret-key', {
|
||||
const accessToken = jwt.sign({ userId: user.id }, secretKey, {
|
||||
expiresIn: '1h',
|
||||
});
|
||||
res.status(200).json({ access_token: accessToken, user });
|
||||
|
||||
@@ -2,12 +2,13 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const defineUser = require('../../models/users');
|
||||
const { DataTypes } = require('sequelize');
|
||||
const roles = require('./roles');
|
||||
const { roles, defaultDangerKey } = require('./authSettings');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
module.exports = function (sequelize) {
|
||||
const User = defineUser(sequelize, DataTypes);
|
||||
const secretKey = process.env.SECRET_KEY || defaultDangerKey;
|
||||
|
||||
router.post('/signup', async (req, res) => {
|
||||
try {
|
||||
@@ -27,7 +28,7 @@ module.exports = function (sequelize) {
|
||||
role: initialRole,
|
||||
});
|
||||
|
||||
const accessToken = jwt.sign({ userId: user.id }, 'your-secret-key', {
|
||||
const accessToken = jwt.sign({ userId: user.id }, secretKey, {
|
||||
expiresIn: '1h',
|
||||
});
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const defineProject = require('../../models/projects');
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { verifySinedIn } = require('../../middleware/auth');
|
||||
|
||||
module.exports = function (sequelize) {
|
||||
const Project = defineProject(sequelize, DataTypes);
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
router.get('/', verifySinedIn, async (req, res) => {
|
||||
try {
|
||||
const projects = await Project.findAll();
|
||||
res.json(projects);
|
||||
|
||||
@@ -3,19 +3,25 @@ const router = express.Router();
|
||||
const defineProject = require('../../models/projects');
|
||||
const defineFolder = require('../../models/folders');
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { verifySinedIn } = require('../../middleware/auth');
|
||||
|
||||
module.exports = function (sequelize) {
|
||||
const Project = defineProject(sequelize, DataTypes);
|
||||
const Folder = defineFolder(sequelize, DataTypes);
|
||||
Project.hasMany(Folder, { foreignKey: 'projectId' });
|
||||
|
||||
router.get('/:projectId', async (req, res) => {
|
||||
router.get('/:projectId', verifySinedIn, async (req, res) => {
|
||||
const projectId = req.params.projectId;
|
||||
|
||||
if (!projectId) {
|
||||
return res.status(400).json({ error: 'projectId is required' });
|
||||
}
|
||||
|
||||
// if project is private, only project owner can access
|
||||
if (!project.isPublic && project.userId !== req.userId) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
try {
|
||||
const project = await Project.findByPk(projectId, {
|
||||
include: [
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"edit_project": "Edit Project",
|
||||
"delete_project": "Delete Project",
|
||||
"id": "ID",
|
||||
"publicity": "Publicity",
|
||||
"name": "Name",
|
||||
"detail": "Detail",
|
||||
"last_update": "Last update",
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"edit_project": "プロジェクトの編集",
|
||||
"delete_project": "プロジェクトの削除",
|
||||
"id": "ID",
|
||||
"publicity": "公開",
|
||||
"name": "名前",
|
||||
"detail": "詳細",
|
||||
"last_update": "最終更新",
|
||||
|
||||
@@ -23,17 +23,16 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props
|
||||
router.push(`/`, { locale: locale });
|
||||
};
|
||||
|
||||
let userAvatar =
|
||||
context.token && context.token.user ? (
|
||||
<Avatar
|
||||
size={16}
|
||||
name={context.token.user.username}
|
||||
variant="beam"
|
||||
colors={['#0A0310', '#49007E', '#FF005B', '#FF7D10', '#FFB238']}
|
||||
/>
|
||||
) : (
|
||||
<User size={16} />
|
||||
);
|
||||
let userAvatar = context.isSignedIn() ? (
|
||||
<Avatar
|
||||
size={16}
|
||||
name={context.token.user.username}
|
||||
variant="beam"
|
||||
colors={['#0A0310', '#49007E', '#FF005B', '#FF7D10', '#FFB238']}
|
||||
/>
|
||||
) : (
|
||||
<User size={16} />
|
||||
);
|
||||
|
||||
const signinItems = [
|
||||
{
|
||||
@@ -81,10 +80,10 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button size="sm" variant="light" startContent={userAvatar} endContent={<ChevronDown size={16} />}>
|
||||
{context.token && context.token.user ? context.token.user.username : messages.signIn}
|
||||
{context.isSignedIn() ? context.token.user.username : messages.signIn}
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
{context.token && context.token.user ? (
|
||||
{context.isSignedIn() ? (
|
||||
<DropdownMenu aria-label="account actions when sign in">
|
||||
{signinItems.map((entry) => (
|
||||
<DropdownItem key={entry.uid} title={entry.title} startContent={entry.icon} onPress={entry.onPress} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import { createContext, useState, useEffect } from 'react';
|
||||
import { TokenType } from '@/types/user';
|
||||
import { TokenContextType, TokenType } from '@/types/user';
|
||||
import { TokenProps } from '@/types/user';
|
||||
import { useRouter, usePathname } from '@/src/navigation';
|
||||
|
||||
@@ -15,8 +15,17 @@ function removeTokenFromLocalStorage() {
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEY);
|
||||
}
|
||||
|
||||
const defaultTokenContext = {};
|
||||
const TokenContext = createContext(defaultTokenContext);
|
||||
const defaultContext = {
|
||||
token: {
|
||||
access_token: '',
|
||||
user: null,
|
||||
},
|
||||
isSignedIn: () => false,
|
||||
setToken: (token: TokenType) => {},
|
||||
storeTokenToLocalStorage,
|
||||
removeTokenFromLocalStorage,
|
||||
};
|
||||
const TokenContext = createContext<TokenContextType>(defaultContext);
|
||||
|
||||
const TokenProvider = ({ locale, children }: TokenProps) => {
|
||||
const router = useRouter();
|
||||
@@ -26,8 +35,14 @@ const TokenProvider = ({ locale, children }: TokenProps) => {
|
||||
access_token: '',
|
||||
user: null,
|
||||
});
|
||||
|
||||
const isSignedIn = () => {
|
||||
return token && token.user && token.user.username ? true : false;
|
||||
};
|
||||
|
||||
const tokenContext = {
|
||||
token,
|
||||
isSignedIn,
|
||||
setToken,
|
||||
storeTokenToLocalStorage,
|
||||
removeTokenFromLocalStorage,
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function AccountPage({ messages, locale }: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{context.token && context.token.user && (
|
||||
{context.isSignedIn() && (
|
||||
<Card className="w-[600px] mt-16 mx-3">
|
||||
<CardHeader className="flex gap-6">
|
||||
<Avatar
|
||||
|
||||
@@ -20,8 +20,11 @@ export default function ProjectsPage({ messages, locale }: Props) {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDataEffect() {
|
||||
if (!context.isSignedIn()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await fetchProjects();
|
||||
const data = await fetchProjects(context.token.access_token);
|
||||
setProjects(data);
|
||||
} catch (error: any) {
|
||||
console.error('Error in effect:', error.message);
|
||||
@@ -29,14 +32,14 @@ export default function ProjectsPage({ messages, locale }: Props) {
|
||||
}
|
||||
|
||||
fetchDataEffect();
|
||||
}, []);
|
||||
}, [context]);
|
||||
|
||||
// dialog
|
||||
const [isNeedSignedInDialogOpen, setIsNeedSignedInDialogOpen] = useState(false);
|
||||
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false);
|
||||
const [editingProject, setEditingProject] = useState<ProjectType | null>(null);
|
||||
const openDialogForCreate = () => {
|
||||
if (!context.token || !context.token.user) {
|
||||
if (!context.isSignedIn()) {
|
||||
setIsNeedSignedInDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TableRow,
|
||||
TableCell,
|
||||
Button,
|
||||
Chip,
|
||||
DropdownTrigger,
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
@@ -29,8 +30,8 @@ type Props = {
|
||||
export default function ProjectsTable({ projects, onEditProject, onDeleteProject, messages, locale }: Props) {
|
||||
const headerColumns = [
|
||||
{ name: messages.id, uid: 'id', sortable: true },
|
||||
{ name: messages.publicity, uid: 'isPublic', sortable: true },
|
||||
{ name: messages.name, uid: 'name', sortable: true },
|
||||
{ name: messages.detail, uid: 'detail', sortable: true },
|
||||
{ name: messages.lastUpdate, uid: 'updatedAt', sortable: true },
|
||||
{ name: messages.actions, uid: 'actions' },
|
||||
];
|
||||
@@ -60,18 +61,19 @@ export default function ProjectsTable({ projects, onEditProject, onDeleteProject
|
||||
switch (columnKey) {
|
||||
case 'id':
|
||||
return <span>{cellValue}</span>;
|
||||
case 'isPublic':
|
||||
return cellValue ? <Chip size="sm">{messages.public}</Chip> : <></>;
|
||||
case 'name':
|
||||
const maxLength = 30;
|
||||
const truncatedDetail = truncateText(project.detail, maxLength);
|
||||
return (
|
||||
<Link href={`/projects/${project.id}/home`} locale={locale} className={NextUiLinkClasses}>
|
||||
{cellValue}
|
||||
</Link>
|
||||
);
|
||||
case 'detail':
|
||||
const maxLength = 20;
|
||||
const truncatedValue = truncateText(cellValue, maxLength);
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{truncatedValue}</div>
|
||||
<div>
|
||||
<Link href={`/projects/${project.id}/home`} locale={locale} className={NextUiLinkClasses}>
|
||||
{cellValue}
|
||||
</Link>
|
||||
<div className="text-xs text-default-500">
|
||||
<div>{truncatedDetail}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'updatedAt':
|
||||
|
||||
@@ -30,7 +30,6 @@ export default function RunsTable({ projectId, runs, onDeleteRun, messages, loca
|
||||
const headerColumns = [
|
||||
{ name: messages.id, uid: 'id', sortable: true },
|
||||
{ name: messages.name, uid: 'name', sortable: true },
|
||||
{ name: messages.description, uid: 'description', sortable: true },
|
||||
{ name: messages.lastUpdate, uid: 'updatedAt', sortable: true },
|
||||
{ name: messages.actions, uid: 'actions' },
|
||||
];
|
||||
@@ -61,17 +60,16 @@ export default function RunsTable({ projectId, runs, onDeleteRun, messages, loca
|
||||
case 'id':
|
||||
return <span>{cellValue}</span>;
|
||||
case 'name':
|
||||
const maxLength = 30;
|
||||
const truncatedDescription = truncateText(run.description, maxLength);
|
||||
return (
|
||||
<Link href={`/projects/${projectId}/runs/${run.id}`} locale={locale} className={NextUiLinkClasses}>
|
||||
{cellValue}
|
||||
</Link>
|
||||
);
|
||||
case 'detail':
|
||||
const maxLength = 20;
|
||||
const truncatedValue = truncateText(cellValue, maxLength);
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>{truncatedValue}</div>
|
||||
<div>
|
||||
<Link href={`/projects/${projectId}/runs/${run.id}`} locale={locale} className={NextUiLinkClasses}>
|
||||
{cellValue}
|
||||
</Link>
|
||||
<div className="text-xs text-default-500">
|
||||
<div>{truncatedDescription}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'updatedAt':
|
||||
|
||||
@@ -10,6 +10,7 @@ export default function Page(params: { locale: string }) {
|
||||
editProject: t('edit_project'),
|
||||
deleteProject: t('delete_project'),
|
||||
id: t('id'),
|
||||
publicity: t('publicity'),
|
||||
name: t('name'),
|
||||
detail: t('detail'),
|
||||
lastUpdate: t('last_update'),
|
||||
|
||||
@@ -4,7 +4,7 @@ const apiServer = Config.apiServer;
|
||||
/**
|
||||
* fetch project records
|
||||
*/
|
||||
async function fetchProjects() {
|
||||
async function fetchProjects(jwt: string) {
|
||||
const url = `${apiServer}/projects`;
|
||||
|
||||
try {
|
||||
@@ -12,6 +12,7 @@ async function fetchProjects() {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: jwt,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export type ProjectsMessages = {
|
||||
editProject: string;
|
||||
deleteProject: string;
|
||||
id: string;
|
||||
publicity: string;
|
||||
name: string;
|
||||
detail: string;
|
||||
lastUpdate: string;
|
||||
|
||||
@@ -22,6 +22,7 @@ export type TokenContextType = {
|
||||
access_token: string;
|
||||
user: UserType;
|
||||
};
|
||||
isSignedIn: () => Boolean;
|
||||
setToken: (token: TokenType) => {};
|
||||
storeTokenToLocalStorage: (token: TokenType) => {};
|
||||
removeTokenFromLocalStorage: () => {};
|
||||
|
||||
Reference in New Issue
Block a user