add login
This commit is contained in:
parent
f70cfd041a
commit
a72225aff7
@ -25,6 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.11.2",
|
"@material-ui/core": "^4.11.2",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
"knex": "^0.21.14",
|
"knex": "^0.21.14",
|
||||||
"koa": "^2.13.0",
|
"koa": "^2.13.0",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
@ -44,6 +45,7 @@
|
|||||||
"@babel/preset-env": "^7.12.11",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@babel/preset-react": "^7.12.10",
|
"@babel/preset-react": "^7.12.10",
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"@types/knex": "^0.16.1",
|
"@types/knex": "^0.16.1",
|
||||||
"@types/koa": "^2.11.6",
|
"@types/koa": "^2.11.6",
|
||||||
"@types/koa-bodyparser": "^4.3.0",
|
"@types/koa-bodyparser": "^4.3.0",
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
{
|
{
|
||||||
"path":["data"]
|
"path": [
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"localmode": true,
|
||||||
|
"guest": false,
|
||||||
|
"jwt_secretkey": "itsRandom"
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ export class ClientContentAccessor implements ContentAccessor{
|
|||||||
* not implement
|
* not implement
|
||||||
*/
|
*/
|
||||||
async findListByBasePath(basepath: string): Promise<Content[]>{
|
async findListByBasePath(basepath: string): Promise<Content[]>{
|
||||||
throw new Error("");
|
throw new Error("not implement");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
async update(c: Partial<Content> & { id: number; }): Promise<boolean>{
|
async update(c: Partial<Content> & { id: number; }): Promise<boolean>{
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
import React, { createContext, useRef, useState } from 'react';
|
import React, { createContext, useRef, useState } from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
import {BrowserRouter, Route, Switch as RouterSwitch} from 'react-router-dom';
|
import {BrowserRouter, Route, Switch as RouterSwitch} from 'react-router-dom';
|
||||||
import { Gallery, ContentAbout} from './page/mod';
|
import { Gallery, ContentAbout, LoginPage, NotFoundPage} from './page/mod';
|
||||||
import {BackLinkContext} from './state';
|
import {UserContext} from './state';
|
||||||
|
|
||||||
import './css/style.css';
|
import './css/style.css';
|
||||||
|
|
||||||
const FooProfile = ()=><div>test profile</div>;
|
const FooProfile = ()=><div>test profile</div>;
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [path,setPath] = useState("/");
|
const [user,setUser] = useState("");
|
||||||
|
const [userPermission,setUserPermission] = useState<string[]>([]);
|
||||||
return (
|
return (
|
||||||
<BackLinkContext.Provider value={{path:path,setPath:setPath}}>
|
<UserContext.Provider value={{
|
||||||
|
username:user,
|
||||||
|
setUsername:setUser,
|
||||||
|
permission:userPermission,
|
||||||
|
setPermission:setUserPermission}}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<RouterSwitch>
|
<RouterSwitch>
|
||||||
<Route path="/" exact render={()=><Gallery />}></Route>
|
<Route path="/" exact render={()=><Gallery />}></Route>
|
||||||
<Route path="/search" render={()=><Gallery />}></Route>
|
<Route path="/search" render={()=><Gallery />}></Route>
|
||||||
<Route path="/doc" render={(prop)=><ContentAbout {...prop}/>}></Route>
|
<Route path="/doc" render={(prop)=><ContentAbout {...prop}/>}></Route>
|
||||||
|
<Route path="/login" render={()=><LoginPage></LoginPage>}/>
|
||||||
<Route path="/profile" component={FooProfile}></Route>
|
<Route path="/profile" component={FooProfile}></Route>
|
||||||
<Route>
|
<Route>
|
||||||
<div>404 Not Found</div>
|
<NotFoundPage/>
|
||||||
</Route>
|
</Route>
|
||||||
</RouterSwitch>
|
</RouterSwitch>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</BackLinkContext.Provider>);
|
</UserContext.Provider>);
|
||||||
};
|
};
|
||||||
|
|
||||||
ReactDom.render(
|
ReactDom.render(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
import React, { ReactNode, useState } from 'react';
|
import React, { ReactNode, useContext, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer,
|
Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer,
|
||||||
AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem,
|
AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem,
|
||||||
@ -8,6 +8,7 @@ import {
|
|||||||
import { makeStyles, Theme, useTheme, fade } from '@material-ui/core/styles';
|
import { makeStyles, Theme, useTheme, fade } from '@material-ui/core/styles';
|
||||||
import { ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle } from '@material-ui/icons';
|
import { ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle } from '@material-ui/icons';
|
||||||
import { Link as RouterLink, useRouteMatch } from 'react-router-dom';
|
import { Link as RouterLink, useRouteMatch } from 'react-router-dom';
|
||||||
|
import { UserContext } from '../state';
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
@ -106,7 +107,6 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||||||
|
|
||||||
export const Headline = (prop: {
|
export const Headline = (prop: {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode,
|
||||||
isLogin?: boolean,
|
|
||||||
classes?:{
|
classes?:{
|
||||||
content?:string,
|
content?:string,
|
||||||
toolbar?:string,
|
toolbar?:string,
|
||||||
@ -122,7 +122,9 @@ export const Headline = (prop: {
|
|||||||
const handleProfileMenuClose = () => setAnchorEl(null);
|
const handleProfileMenuClose = () => setAnchorEl(null);
|
||||||
const isProfileMenuOpened = Boolean(anchorEl);
|
const isProfileMenuOpened = Boolean(anchorEl);
|
||||||
const menuId = 'primary-search-account-menu';
|
const menuId = 'primary-search-account-menu';
|
||||||
const isLogin = prop.isLogin || false;
|
const user_ctx = useContext(UserContext);
|
||||||
|
const isLogin = user_ctx.username !== "";
|
||||||
|
|
||||||
const renderProfileMenu = (<Menu
|
const renderProfileMenu = (<Menu
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
anchorOrigin={{ horizontal: 'right', vertical: "top" }}
|
anchorOrigin={{ horizontal: 'right', vertical: "top" }}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {List, ListItem, ListItemIcon, Tooltip, ListItemText} from '@material-ui/core';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Link as RouterLink} from 'react-router-dom';
|
import {List, ListItem, ListItemIcon, Tooltip, ListItemText} from '@material-ui/core';
|
||||||
import {LocationDescriptorObject} from 'history';
|
import {ArrowBack as ArrowBackIcon} from '@material-ui/icons';
|
||||||
|
import {Link as RouterLink, useHistory} from 'react-router-dom';
|
||||||
|
|
||||||
export const NavItem = (props:{name:string,to:string, icon:React.ReactElement<any,any>})=>{
|
export const NavItem = (props:{name:string,to:string, icon:React.ReactElement<any,any>})=>{
|
||||||
|
const history = useHistory();
|
||||||
return (<ListItem button key={props.name} component={RouterLink} to={props.to}>
|
return (<ListItem button key={props.name} component={RouterLink} to={props.to}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Tooltip title={props.name.toLocaleLowerCase()} placement="bottom">
|
<Tooltip title={props.name.toLocaleLowerCase()} placement="bottom">
|
||||||
@ -19,3 +20,7 @@ export const NavList = (props: {children?:React.ReactNode})=>{
|
|||||||
{props.children}
|
{props.children}
|
||||||
</List>);
|
</List>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BackItem = (props:{to?:string})=>{
|
||||||
|
return <NavItem name="Back" to={props.to ?? "/"} icon={<ArrowBackIcon></ArrowBackIcon>}/>;
|
||||||
|
}
|
11
src/client/page/404.tsx
Normal file
11
src/client/page/404.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Typography} from '@material-ui/core';
|
||||||
|
import {ArrowBack as ArrowBackIcon} from '@material-ui/icons';
|
||||||
|
import { Headline, BackItem, NavList } from '../component/mod';
|
||||||
|
|
||||||
|
export const NotFoundPage = ()=>{
|
||||||
|
const menu = (<NavList><BackItem to="/"/></NavList>);
|
||||||
|
return <Headline menu={menu}>
|
||||||
|
<Typography variant='h2'>404 Not Found</Typography>
|
||||||
|
</Headline>
|
||||||
|
};
|
@ -5,8 +5,7 @@ import { LoadingCircle } from '../component/loading';
|
|||||||
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
|
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
|
||||||
import {ArrowBack as ArrowBackIcon } from '@material-ui/icons';
|
import {ArrowBack as ArrowBackIcon } from '@material-ui/icons';
|
||||||
import { getPresenter } from './reader/reader';
|
import { getPresenter } from './reader/reader';
|
||||||
import { ContentInfo, Headline, NavItem, NavList } from '../component/mod';
|
import { BackItem, ContentInfo, Headline, NavItem, NavList } from '../component/mod';
|
||||||
import {BackLinkContext} from '../state';
|
|
||||||
|
|
||||||
export const makeContentInfoUrl = (id: number) => `/doc/${id}`;
|
export const makeContentInfoUrl = (id: number) => `/doc/${id}`;
|
||||||
export const makeMangaReaderUrl = (id: number) => `/doc/${id}/reader`;
|
export const makeMangaReaderUrl = (id: number) => `/doc/${id}/reader`;
|
||||||
@ -35,29 +34,20 @@ export const ContentAbout = (prop: { match: MatchType }) => {
|
|||||||
}
|
}
|
||||||
const id = Number.parseInt(match.params['id']);
|
const id = Number.parseInt(match.params['id']);
|
||||||
const [info, setInfo] = useState<ContentState>({ content: undefined, notfound:false });
|
const [info, setInfo] = useState<ContentState>({ content: undefined, notfound:false });
|
||||||
const location = useLocation();
|
const history = useHistory();
|
||||||
console.log("state : "+location.state);
|
|
||||||
const menu_list = (link?:string)=>(
|
const menu_list = (link?:string)=>(
|
||||||
<NavList>
|
<NavList>
|
||||||
<BackLinkContext.Consumer>
|
<BackItem to={link}/>
|
||||||
{
|
|
||||||
(ctx) => link === undefined ?
|
|
||||||
<NavItem name="Back" to={ctx.path} icon={<ArrowBackIcon/>}/>
|
|
||||||
: <NavItem name="Back" to={link} icon={<ArrowBackIcon/>}/>
|
|
||||||
}
|
|
||||||
</BackLinkContext.Consumer>
|
|
||||||
</NavList>
|
</NavList>
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
console.log("mount content about");
|
|
||||||
if (!isNaN(id)) {
|
if (!isNaN(id)) {
|
||||||
const c = await ContentAccessor.findById(id);
|
const c = await ContentAccessor.findById(id);
|
||||||
setInfo({ content: c, notfound: c === undefined });
|
setInfo({ content: c, notfound: c === undefined });
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
return ()=>{console.log("unmount content about")}
|
|
||||||
}, []);
|
}, []);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { NavList, NavItem, Headline } from '../component/mod';
|
import { NavList, NavItem, Headline, BackItem } from '../component/mod';
|
||||||
import {ArrowBack as ArrowBackIcon} from '@material-ui/icons';
|
import {ArrowBack as ArrowBackIcon, Settings as SettingIcon,
|
||||||
|
Collections as CollectionIcon, VideoLibrary as VideoIcon, Home as HomeIcon} from '@material-ui/icons';
|
||||||
import {GalleryInfo} from '../component/mod';
|
import {GalleryInfo} from '../component/mod';
|
||||||
import {BackLinkContext} from '../state';
|
|
||||||
import {useLocation} from 'react-router-dom';
|
import {useLocation} from 'react-router-dom';
|
||||||
import { QueryStringToMap } from '../accessor/util';
|
import { QueryStringToMap } from '../accessor/util';
|
||||||
|
import { Divider } from '@material-ui/core';
|
||||||
|
|
||||||
export const Gallery = ()=>{
|
export const Gallery = ()=>{
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const backctx = useContext(BackLinkContext);
|
|
||||||
backctx.setPath("/");
|
|
||||||
|
|
||||||
const query = QueryStringToMap(location.search);
|
const query = QueryStringToMap(location.search);
|
||||||
|
|
||||||
const menu_list = (<NavList>
|
const menu_list = (<NavList>
|
||||||
{Object.keys(query).length !== 0 && <NavItem name="Back" to="/" icon={<ArrowBackIcon></ArrowBackIcon>}></NavItem>}
|
{location.search !== "" && <><BackItem/> <Divider/></>}
|
||||||
|
<NavItem name="All" to="/" icon={<HomeIcon/>}/>
|
||||||
|
<NavItem name="Manga" to="/search?content_type=manga" icon={<CollectionIcon/>}></NavItem>
|
||||||
|
<NavItem name="Video" to="/search?content_type=video" icon={<VideoIcon/>}/>
|
||||||
|
<Divider/>
|
||||||
|
<NavItem name="Settings" to="/setting" icon={<SettingIcon/>}/>
|
||||||
</NavList>);
|
</NavList>);
|
||||||
|
|
||||||
return (<Headline menu={menu_list}>
|
return (<Headline menu={menu_list}>
|
||||||
|
70
src/client/page/login.tsx
Normal file
70
src/client/page/login.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import {Headline} from '../component/mod';
|
||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogContentText,
|
||||||
|
DialogTitle, MenuList, Paper, TextField, Typography, useTheme } from '@material-ui/core';
|
||||||
|
import { UserContext } from '../state';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const LoginPage = ()=>{
|
||||||
|
const theme = useTheme();
|
||||||
|
const [userLoginInfo,setUserLoginInfo]= useState({username:"",password:""});
|
||||||
|
const [openDialog,setOpenDialog] = useState({open:false,message:""});
|
||||||
|
const {username,setUsername,permission,setPermission} = useContext(UserContext);
|
||||||
|
const history = useHistory();
|
||||||
|
const handleDialogClose = ()=>{
|
||||||
|
setOpenDialog({...openDialog,open:false});
|
||||||
|
}
|
||||||
|
const doLogin = async ()=>{
|
||||||
|
const res = await fetch('/user/login',{
|
||||||
|
method:'POST',
|
||||||
|
body:JSON.stringify(userLoginInfo),
|
||||||
|
headers:{"content-type":"application/json"}
|
||||||
|
});
|
||||||
|
try{
|
||||||
|
const b = await res.json();
|
||||||
|
if(res.status !== 200){
|
||||||
|
setOpenDialog({open:true,message: b.detail});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUsername(b.username);
|
||||||
|
setPermission(b.permission);
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
if(e instanceof Error){
|
||||||
|
console.error(e);
|
||||||
|
setOpenDialog({open:true,message:e.message});
|
||||||
|
}
|
||||||
|
else console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.push("/");
|
||||||
|
}
|
||||||
|
const menu = (<MenuList>
|
||||||
|
</MenuList>);
|
||||||
|
return <Headline menu={menu}>
|
||||||
|
<Paper style={{ width: theme.spacing(40), padding: theme.spacing(4), alignSelf:'center'}}>
|
||||||
|
<Typography variant="h4">Login</Typography>
|
||||||
|
<div style={{minHeight:theme.spacing(2)}}></div>
|
||||||
|
<form style={{display:'flex', flexFlow:'column', alignItems:'stretch'}}>
|
||||||
|
<TextField label="username" onChange={(e)=>setUserLoginInfo({...userLoginInfo,username:e.target.value ?? "",})}></TextField>
|
||||||
|
<TextField label="password" type="password"
|
||||||
|
onChange={(e)=>setUserLoginInfo({...userLoginInfo,password:e.target.value ?? "",})}/>
|
||||||
|
<div style={{minHeight:theme.spacing(2)}}></div>
|
||||||
|
<div style={{display:'flex'}}>
|
||||||
|
<Button onClick={doLogin}>login</Button>
|
||||||
|
<Button>signin</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
<Dialog open={openDialog.open}
|
||||||
|
onClose={handleDialogClose}>
|
||||||
|
<DialogTitle>Login Failed</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>detail : {openDialog.message}</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleDialogClose} color="primary" autoFocus>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Headline>
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
export * from './contentinfo';
|
export * from './contentinfo';
|
||||||
export * from './gallery';
|
export * from './gallery';
|
||||||
|
export * from './login';
|
||||||
|
export * from './404';
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
import React, { createContext, useRef, useState } from 'react';
|
import React, { createContext, useRef, useState } from 'react';
|
||||||
|
|
||||||
export const BackLinkContext = createContext({path:"",setPath:(s:string)=>{}});
|
export const UserContext = createContext({
|
||||||
|
username:"",
|
||||||
|
permission:["openContent"],
|
||||||
|
setUsername:(s:string)=>{},
|
||||||
|
setPermission:(permission:string[])=>{}
|
||||||
|
});
|
@ -8,7 +8,7 @@ export async function connectDB(){
|
|||||||
if(env != "production" && env != "development"){
|
if(env != "production" && env != "development"){
|
||||||
throw new Error("process unknown value in NODE_ENV: must be either \"development\" or \"production\"");
|
throw new Error("process unknown value in NODE_ENV: must be either \"development\" or \"production\"");
|
||||||
}
|
}
|
||||||
const init_need = existsSync(config[env].connection.filename);
|
const init_need = !existsSync(config[env].connection.filename);
|
||||||
const knex = Knex(config[env]);
|
const knex = Knex(config[env]);
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
for(;;){
|
for(;;){
|
||||||
@ -30,6 +30,7 @@ export async function connectDB(){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(init_need){
|
if(init_need){
|
||||||
|
console.log("first execute: initialize database...");
|
||||||
const migrate = await import("../migrations/initial");
|
const migrate = await import("../migrations/initial");
|
||||||
await migrate.up(knex);
|
await migrate.up(knex);
|
||||||
}
|
}
|
||||||
|
76
src/login.ts
Normal file
76
src/login.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {sign, decode, verify} from 'jsonwebtoken';
|
||||||
|
import Koa from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { sendError } from './route/error_handler';
|
||||||
|
import Knex from 'knex'
|
||||||
|
import { createKnexUserController } from './db/mod';
|
||||||
|
import { request } from 'http';
|
||||||
|
import { get_setting } from './setting';
|
||||||
|
import { IUser } from './model/mod';
|
||||||
|
|
||||||
|
const loginTokenName = 'access_token'
|
||||||
|
|
||||||
|
export const createLoginMiddleware = (knex: Knex)=>{
|
||||||
|
const userController = createKnexUserController(knex);
|
||||||
|
return async (ctx: Koa.Context,next: Koa.Next)=>{
|
||||||
|
const setting = get_setting();
|
||||||
|
const secretKey = setting.jwt_secretkey;
|
||||||
|
const body = ctx.request.body;
|
||||||
|
if(!('username' in body)||!('password' in body)){
|
||||||
|
sendError(400,"invalid form : username or password is not found in query.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const username = body['username'];
|
||||||
|
const password = body['password'];
|
||||||
|
if(typeof username !== "string" || typeof password !== "string"){
|
||||||
|
sendError(400,"invalid form : username or password is not string")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const user = await userController.findUser(username);
|
||||||
|
if(user === undefined){
|
||||||
|
sendError(401,"not authorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!user.password.check_password(password)){
|
||||||
|
sendError(401,"not authorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userPermission = await user.get_permissions();
|
||||||
|
const payload = sign({
|
||||||
|
username: user.username,
|
||||||
|
permission: userPermission
|
||||||
|
},secretKey,{expiresIn:'3d'});
|
||||||
|
ctx.cookies.set(loginTokenName,payload,{httpOnly:true, secure: !setting.localmode,sameSite:'strict'});
|
||||||
|
ctx.body = {ok:true, username: user.username, permission: userPermission}
|
||||||
|
console.log(`${username} logined`);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const LogoutMiddleware = (ctx:Koa.Context,next:Koa.Next)=>{
|
||||||
|
ctx.cookies.set(loginTokenName,undefined);
|
||||||
|
ctx.body = {ok:true};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
export const UserMiddleWare = async (ctx:Koa.Context,next:Koa.Next)=>{
|
||||||
|
const secretKey = get_setting().jwt_secretkey;
|
||||||
|
const payload = ctx.cookies.get(loginTokenName);
|
||||||
|
if(payload == undefined){
|
||||||
|
ctx.state['user'] = undefined;
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
ctx.state['user'] = verify(payload,secretKey);
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAdmin = async(knex : Knex)=>{
|
||||||
|
const cntr = createKnexUserController(knex);
|
||||||
|
const admin = await cntr.findUser("admin");
|
||||||
|
if(admin === undefined){
|
||||||
|
throw new Error("initial process failed!");//???
|
||||||
|
}
|
||||||
|
return admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isAdminFirst = (admin: IUser)=>{
|
||||||
|
return admin.password.hash === "unchecked" && admin.password.salt === "unchecked";
|
||||||
|
}
|
@ -34,6 +34,7 @@ const ContentQueryHandler = (controller : ContentAccessor) => async (ctx: Contex
|
|||||||
const limit = ParseQueryNumber(ctx.query['limit']);
|
const limit = ParseQueryNumber(ctx.query['limit']);
|
||||||
const cursor = ParseQueryNumber(ctx.query['cursor']);
|
const cursor = ParseQueryNumber(ctx.query['cursor']);
|
||||||
const word: string|undefined = ctx.query['word'];
|
const word: string|undefined = ctx.query['word'];
|
||||||
|
const content_type:string|undefined = ctx.query['content_type'];
|
||||||
const offset = ParseQueryNumber(ctx.query['offset']);
|
const offset = ParseQueryNumber(ctx.query['offset']);
|
||||||
if(limit === NaN || cursor === NaN || offset === NaN){
|
if(limit === NaN || cursor === NaN || offset === NaN){
|
||||||
sendError(400,"parameter limit, cursor or offset is not a number");
|
sendError(400,"parameter limit, cursor or offset is not a number");
|
||||||
@ -51,7 +52,8 @@ const ContentQueryHandler = (controller : ContentAccessor) => async (ctx: Contex
|
|||||||
cursor: cursor,
|
cursor: cursor,
|
||||||
eager_loading: true,
|
eager_loading: true,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
use_offset: use_offset
|
use_offset: use_offset,
|
||||||
|
content_type:content_type,
|
||||||
};
|
};
|
||||||
let content = await controller.findList(option);
|
let content = await controller.findList(option);
|
||||||
ctx.body = content;
|
ctx.body = content;
|
||||||
|
@ -11,56 +11,64 @@ import { createKnexContentsAccessor } from './db/contents';
|
|||||||
import bodyparser from 'koa-bodyparser';
|
import bodyparser from 'koa-bodyparser';
|
||||||
import {error_handler} from './route/error_handler';
|
import {error_handler} from './route/error_handler';
|
||||||
|
|
||||||
|
import {UserMiddleWare,createLoginMiddleware, isAdminFirst, getAdmin, LogoutMiddleware} from './login';
|
||||||
|
|
||||||
|
import {createInterface as createReadlineInterface} from 'readline';
|
||||||
|
|
||||||
//let Koa = require("koa");
|
//let Koa = require("koa");
|
||||||
async function main(){
|
async function main(){
|
||||||
|
let settings = get_setting();
|
||||||
|
let db = await connectDB();
|
||||||
|
const userAdmin = await getAdmin(db);
|
||||||
|
if(await isAdminFirst(userAdmin)){
|
||||||
|
const rl = createReadlineInterface({input:process.stdin,output:process.stdout});
|
||||||
|
rl.setPrompt("put admin password : ");
|
||||||
|
rl.prompt();
|
||||||
|
const pw = await new Promise((res:(data:string)=>void,err)=>{
|
||||||
|
rl.on('line',(data)=>res(data));
|
||||||
|
});
|
||||||
|
userAdmin.reset_password(pw);
|
||||||
|
}
|
||||||
let app = new Koa();
|
let app = new Koa();
|
||||||
app.use(bodyparser());
|
app.use(bodyparser());
|
||||||
app.use(error_handler);
|
app.use(error_handler);
|
||||||
|
app.use(UserMiddleWare);
|
||||||
|
//app.use(ctx=>{ctx.state['setting'] = settings});
|
||||||
|
|
||||||
const index_html = readFileSync("index.html");
|
const index_html = readFileSync("index.html");
|
||||||
let router = new Router();
|
let router = new Router();
|
||||||
|
|
||||||
let settings = get_setting();
|
|
||||||
|
|
||||||
let db = await connectDB();
|
|
||||||
let watcher = new Watcher(settings.path[0]);
|
let watcher = new Watcher(settings.path[0]);
|
||||||
await watcher.setup([]);
|
await watcher.setup([]);
|
||||||
console.log(settings);
|
const serveindex = (url:string)=>{
|
||||||
router.get('/', async (ctx,next)=>{
|
router.get(url, (ctx)=>{ctx.type = 'html'; ctx.body = index_html;})
|
||||||
ctx.type = "html";
|
|
||||||
ctx.body = index_html;
|
|
||||||
});
|
|
||||||
router.get('/dist/css/style.css',async (ctx,next)=>{
|
|
||||||
ctx.type = "css";
|
|
||||||
ctx.body = createReadStream("dist/css/style.css");
|
|
||||||
});
|
|
||||||
router.get('/dist/js/bundle.js',async (ctx,next)=>{
|
|
||||||
ctx.type = "js";
|
|
||||||
ctx.body = createReadStream("dist/js/bundle.js");
|
|
||||||
});
|
|
||||||
router.get('/dist/js/bundle.js.map',async (ctx,next)=>{
|
|
||||||
ctx.type = "text";
|
|
||||||
ctx.body = createReadStream("dist/js/bundle.js.map");
|
|
||||||
});
|
|
||||||
router.get('/doc/:rest(.*)'
|
|
||||||
,async (ctx,next)=>{
|
|
||||||
ctx.type = "html";
|
|
||||||
ctx.body = index_html;
|
|
||||||
}
|
}
|
||||||
);
|
serveindex('/');
|
||||||
router.get('/search'
|
serveindex('/doc/:rest(.*)');
|
||||||
,async (ctx,next)=>{
|
serveindex('/search');
|
||||||
ctx.type = "html";
|
serveindex('/login');
|
||||||
ctx.body = index_html;
|
|
||||||
}
|
const static_file_server = (path:string,type:string) => {
|
||||||
);
|
router.get('/'+path,async (ctx,next)=>{
|
||||||
let content_router = getContentRouter(createKnexContentsAccessor(db));
|
ctx.type = type; ctx.body = createReadStream(path);
|
||||||
|
})}
|
||||||
|
static_file_server('dist/css/style.css','css');
|
||||||
|
static_file_server('dist/js/bundle.js','js');
|
||||||
|
static_file_server('dist/js/bundle.js.map','text');
|
||||||
|
|
||||||
|
const content_router = getContentRouter(createKnexContentsAccessor(db));
|
||||||
router.use('/content',content_router.routes());
|
router.use('/content',content_router.routes());
|
||||||
router.use('/content',content_router.allowedMethods());
|
router.use('/content',content_router.allowedMethods());
|
||||||
|
|
||||||
|
router.post('/user/login',createLoginMiddleware(db));
|
||||||
|
router.post('/user/logout',LogoutMiddleware);
|
||||||
|
|
||||||
let mm_count = 0;
|
let mm_count = 0;
|
||||||
app.use(async (ctx,next)=>{
|
app.use(async (ctx,next)=>{
|
||||||
console.log(`==========================${mm_count++}`);
|
console.log(`==========================${mm_count++}`);
|
||||||
console.log(`connect ${ctx.ip} : ${ctx.method} ${ctx.url}`);
|
const fromClient = ctx.state['user'] === undefined ? ctx.ip : ctx.state['user'].username;
|
||||||
|
console.log(`${fromClient} : ${ctx.method} ${ctx.url}`);
|
||||||
await next();
|
await next();
|
||||||
//console.log(`404`);
|
//console.log(`404`);
|
||||||
});
|
});
|
||||||
@ -68,7 +76,7 @@ async function main(){
|
|||||||
app.use(router.allowedMethods());
|
app.use(router.allowedMethods());
|
||||||
|
|
||||||
console.log("start server");
|
console.log("start server");
|
||||||
app.listen(8080,"0.0.0.0");
|
app.listen(8080,settings.localmode ? "127.0.0.1" : "0.0.0.0");
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
main();
|
main();
|
@ -1,26 +1,28 @@
|
|||||||
import { Settings } from '@material-ui/icons';
|
import { Settings } from '@material-ui/icons';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
import { readFileSync, writeFileSync } from 'fs';
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
|
||||||
export type Setting = {
|
export type Setting = {
|
||||||
path: string[],
|
path: string[],
|
||||||
initial_admin_password:string,
|
|
||||||
localmode: boolean,
|
localmode: boolean,
|
||||||
guest: boolean,
|
guest: boolean,
|
||||||
|
jwt_secretkey: string
|
||||||
}
|
}
|
||||||
const default_setting:Setting = {
|
const default_setting:Setting = {
|
||||||
path:[],
|
path:[],
|
||||||
initial_admin_password:"admin",
|
|
||||||
localmode: true,
|
localmode: true,
|
||||||
guest:false,
|
guest:false,
|
||||||
|
jwt_secretkey:"itsRandom",
|
||||||
}
|
}
|
||||||
let setting: null|Setting = null;
|
let setting: null|Setting = null;
|
||||||
const setEmptyToDefault = (target:any,default_table:any)=>{
|
|
||||||
|
const setEmptyToDefault = (target:any,default_table:Setting)=>{
|
||||||
let diff_occur = false;
|
let diff_occur = false;
|
||||||
for(const key in default_table){
|
for(const key in default_table){
|
||||||
if(key === undefined || key in target){
|
if(key === undefined || key in target){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
target[key] = default_table[key];
|
target[key] = default_table[key as keyof Setting];
|
||||||
diff_occur = true;
|
diff_occur = true;
|
||||||
}
|
}
|
||||||
return diff_occur;
|
return diff_occur;
|
||||||
@ -28,8 +30,8 @@ const setEmptyToDefault = (target:any,default_table:any)=>{
|
|||||||
|
|
||||||
export const read_setting_from_file = ()=>{
|
export const read_setting_from_file = ()=>{
|
||||||
let ret = JSON.parse(readFileSync("settings.json",{encoding:"utf8"})) as Setting;
|
let ret = JSON.parse(readFileSync("settings.json",{encoding:"utf8"})) as Setting;
|
||||||
const diff_occur = setEmptyToDefault(ret,default_setting);
|
const partial_occur = setEmptyToDefault(ret,default_setting);
|
||||||
if(diff_occur){
|
if(partial_occur){
|
||||||
writeFileSync("settings.json",JSON.stringify(ret));
|
writeFileSync("settings.json",JSON.stringify(ret));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
Loading…
Reference in New Issue
Block a user