Skip to content

Commit

Permalink
feat: user module;http code
Browse files Browse the repository at this point in the history
  • Loading branch information
rosendolu committed May 13, 2024
1 parent 088b8b3 commit 6e73a7d
Show file tree
Hide file tree
Showing 16 changed files with 339 additions and 102 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ logs/*
*.log
*.local
temp/
debug/
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
"source.sortImports": "always",
"source.fixAll.eslint": "always"
},
"cSpell.words": ["rosendofun"]
"cSpell.words": ["rosendofun"],
"outline.showConstants": false,
"breadcrumbs.showConstants": false
}
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,31 @@
[![Star History Chart](https://api.star-history.com/svg?repos=rosendolu/rosendofun.service&type=Timeline)](https://github.com/rosendolu/rosendofun.service#readme)

## Development

1. Config env

Create env file `touch .env.local`

```yaml
SESSION_KEYS=xxxxxx
```

2. Create secret key

```sh
mkdir local
cd local
openssl genpkey -algorithm RSA -out private_key.pem
openssl rsa -pubout -in private_key.pem -out public_key.pem
```

3. Deploy

```sh
npm run prod
```

## [HTTP](doc/http.md)

## [File](doc/file.md)
7 changes: 7 additions & 0 deletions app/common/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const env = process.env;
module.exports = {
ADMIN_WHITELIST: env.ADMIN_WHITELIST.split(','),
SESSION_KEYS: env.SESSION_KEYS,
PORT: env.PORT,
SERVICE_NAME: 'rosendofun.service',
};
12 changes: 7 additions & 5 deletions app/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require('dotenv').config({ path: ['.env', '.env.local'], override: true });

const Koa = require('koa');
const serve = require('koa-static');
const logger = require('./common/logger');
Expand All @@ -8,13 +10,14 @@ const relativeTime = require('dayjs/plugin/relativeTime');
// Extend dayjs with the relativeTime plugin
dayjs.extend(relativeTime);
const { commonHandle, useKoaBody } = require('./middleware');
require('dotenv').config({ path: ['.env', '.env.local'], override: true });
const koaSession = require('koa-session');
const constant = require('./common/constant');
const user = require('./middleware/user');
const path = require('node:path');
const env = require('./common/env');
const app = new Koa();
app.keys = [process.env.SESSION_KEYS];
app.keys = [env.SESSION_KEYS];

app.use(commonHandle());
app.use(
cors({
Expand All @@ -27,7 +30,6 @@ app.use(useKoaBody());
app.use(serve(path.join(constant.rootDir, 'temp/upload')));
app.use(router.routes()).use(router.allowedMethods());

const port = process.env.PORT;
app.listen(port, () => {
logger.info(`Server running at http://localhost:%s`, port);
app.listen(env.PORT, () => {
logger.info(`Server running at http://localhost:%s`, env.PORT);
});
106 changes: 106 additions & 0 deletions app/middleware/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const fs = require('fs');
const path = require('path');
const { getStaticFile } = require('../common/utils');
const logger = require('../common/logger');
const utils = require('../common/utils');
const JSONStream = require('../common/jsonStream');
const { rootDir } = require('../common/constant');
const { assert } = require('console');

module.exports = {
resCode(ctx, next) {
let { code, type } = ctx.params;
code = ~~code;
assert(typeof code === 'number');

ctx.status = code;
switch (code) {
case 401:
if (type == 'digest') {
ctx.set(
'WWW-Authenticate',
'Digest realm="Restricted Access", algorithm="MD5", qop="auth", nonce="' +
Math.random().toString(36).substring(7) +
'"'
);
} else if (type == 'bearer') {
ctx.set('WWW-Authenticate', 'Bearer realm="Restricted Access"');
} else {
ctx.set('WWW-Authenticate', 'Basic realm="Restricted Access"');
}
ctx.body = 'Unauthorized';
break;

default:
ctx.body = {};
break;
}
},
async resStream(ctx, next) {
const { streamType } = ctx.params;
const jsonType = streamType == 'json';
const data = jsonType
? fs.readFileSync(path.resolve('static', 'index.json.text')).toString().split(/\n/)
: Array.from({ length: 3 }).map((val, i) => `This is chunk ${i + 1}`);

ctx.res.writeHead(200, { 'Content-Type': 'text/plain' });

for (let index = 0; index < data.length; index++) {
const chunk = data[index];
ctx.res.write(chunk);
index != data.length - 1 && (await utils.waitForSeconds(3e3));
}
// Optionally, you can end the response after writing all data
ctx.res.end();
},
async resContentType(ctx, next) {
const { contentType } = ctx.params;
switch (contentType) {
case 'html':
ctx.type = 'text/html';
ctx.body = getStaticFile('index.html');
break;
case 'text':
ctx.type = 'text/plain';
ctx.body = getStaticFile('index.txt');
break;
case 'img':
ctx.type = 'image/png';
// ctx.body = fs.readFileSync(path.resolve('static', 'index.png'));
// ctx.body = fs.readFileSync(path.resolve('static', 'index.png')).toString('base64');
ctx.body = getStaticFile('index.png');
break;
case 'video':
ctx.type = 'video/mp4';
ctx.body = getStaticFile('index.mp4');
break;
case 'css':
ctx.type = 'text/css';
ctx.body = getStaticFile('index.css');
break;
case 'xml':
ctx.type = 'application/xml';
ctx.body = getStaticFile('index.xml');
break;
case 'js':
ctx.type = 'application/javascript';
ctx.body = getStaticFile('index.js');
break;
case 'file':
const fileName = 'index.txt';
ctx.set('Content-Disposition', `attachment; filename="${fileName}"`);
ctx.type = 'application/octet-stream';
ctx.body = getStaticFile(fileName);
break;

case 'json':
default:
ctx.type = 'application/json';
// WARNNING json must be write one-time
// ctx.body = getStaticFile('index.json');
ctx.body = JSON.parse(fs.readFileSync(path.resolve(rootDir, 'static/index.json')).toString());
break;
}
await next();
},
};
61 changes: 60 additions & 1 deletion app/middleware/user.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
const { koaBody } = require('koa-body');
const path = require('path');
const logger = require('../common/logger');
const { isProdEnv } = require('../common/constant');
const { isProdEnv, rootDir } = require('../common/constant');
const utils = require('../common/utils');
const fs = require('fs');
const jwt = require('jsonwebtoken');
const dayjs = require('dayjs');
const env = require('../common/env');
const { assert } = require('console');
const PRIVATE_KEY = fs.readFileSync(path.resolve(rootDir, 'local/private_key.pem'));
const PUBLIC_KEY = fs.readFileSync(path.resolve(rootDir, 'local/public_key.pem'));

module.exports = {
userHandle() {
Expand All @@ -18,4 +25,56 @@ module.exports = {
ctx.session.lastVisit = utils.timestamp();
};
},
userInfo(ctx, next) {
const { token, nickname, visitCount, uid, lastVisit } = ctx.session;
ctx.body = {
message: `Last visit ${dayjs().to(dayjs(lastVisit))}!`,
visitCount: visitCount,
lastVisit: lastVisit,
nickname,
token: token || '',
};
},
userLogin(ctx, next) {
// post body
let { username, password } = ctx.request.body;
const auth = ctx.headers['authorization'];
if ((!username || !password) && auth && auth.indexOf('Basic ') !== -1) {
const credentials = Buffer.from(auth.split(' ')[1], 'base64').toString().split(':');
username = credentials[0];
password = credentials[1];
}

if (username && password) {
ctx.session.username = username;
ctx.session.password = password;
// admin jwt token
if (env.ADMIN_WHITELIST.includes(`${username}:${password}`)) {
const payload = { uid: ctx.session.uid, username, timestamp: utils.timestamp() }; // 设置有效载荷
const token = jwt.sign(payload, PRIVATE_KEY, {
algorithm: 'RS256',
issuer: env.SERVICE_NAME,
expiresIn: '5d',
});
ctx.session.token = token;
}
return ctx.redirect(ctx.router.url('userInfo'));
}
ctx.status = 401;
ctx.set('WWW-Authenticate', 'Basic realm="Restricted Access"');
ctx.body = 'Unauthorized';
},
async adminAuth(ctx, next) {
const token = ctx.session.token || '';
try {
const decoded = jwt.verify(token, PUBLIC_KEY, { issuer: env.SERVICE_NAME });
ctx.status = 200;
ctx.body = decoded;
await next();
} catch (err) {
logger.error('adminAuth %s %j', err, ctx.session);
ctx.status = 401;
ctx.body = 'Unauthorized';
}
},
};
79 changes: 0 additions & 79 deletions app/router/content-type.route.js

This file was deleted.

3 changes: 1 addition & 2 deletions app/router/file.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ const fileMiddleware = require('../middleware/file');

const prefix = '/file';
module.exports = router => {
router.post(`${prefix}/upload`, fileMiddleware.uploadFile);
router.get(`${prefix}/list`, fileMiddleware.listFile);
router.post(`${prefix}/upload`, fileMiddleware.uploadFile).get(`${prefix}/list`, fileMiddleware.listFile);
};
9 changes: 9 additions & 0 deletions app/router/http.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const http = require('../middleware/http');

const prefix = '/http';
module.exports = router => {
router
.get(`${prefix}/contentType/:contentType`, http.resContentType)
.get(`${prefix}/res/stream/:streamType`, http.resStream)
.get(`${prefix}/code/:code/:type?`, http.resCode);
};
11 changes: 2 additions & 9 deletions app/router/root.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@ const utils = require('../common/utils');

module.exports = router => {
router.all('/', ctx => {
const { nickname, visitCount, uid, lastVisit } = ctx.session;
ctx.body = {
message: `Last visit ${dayjs().to(dayjs(lastVisit))}!`,
visitCount: visitCount,
lastVisit: lastVisit,
nickname,
uid,
serverTime: utils.timestamp(),
};
ctx.status = 302;
ctx.redirect(router.url('userInfo'));
});
};
9 changes: 9 additions & 0 deletions app/router/user.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const user = require('../middleware/user');

const prefix = '/user';
module.exports = router => {
router
.all('userLogin', `${prefix}/login`, user.userLogin)
.all('userInfo', `${prefix}/info`, user.userInfo)
.all('adminAuth', `${prefix}/auth`, user.adminAuth);
};
1 change: 1 addition & 0 deletions custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ declare namespace NodeJS {
interface ProcessEnv {
SESSION_KEYS: string;
PORT: number;
ADMIN_WHITELIST: string;
}
}
Loading

0 comments on commit 6e73a7d

Please sign in to comment.