├── DockerBuild.md
├── Dockerfile
├── LICENSE
├── README.md
├── admin
├── db
│ ├── password
│ │ └── PasswordNext.sqlite
│ └── user
│ │ ├── user.md
│ │ ├── user.sqlite
│ │ └── user_backup.sqlite
├── index.js
├── package.json
├── routers
│ ├── main.js
│ └── password
│ │ ├── addPassword.js
│ │ ├── deletePassword.js
│ │ ├── editPassword.js
│ │ ├── get2faSecret.js
│ │ ├── get2faToken.js
│ │ ├── getPassword.js
│ │ ├── getRandomPassword.js
│ │ ├── login.js
│ │ ├── main.js
│ │ ├── public
│ │ └── checkToken.js
│ │ ├── register.js
│ │ └── router.js
└── utils
│ ├── database
│ ├── changeSqliteTableColumn.js
│ ├── changeSqliteTableData.js
│ ├── createSqlite.js
│ ├── createSqliteTable.js
│ ├── deleteSqliteTableColumn.js
│ ├── deleteSqliteTableData.js
│ ├── getSqlite.js
│ ├── getSqliteAllTable.js
│ ├── insertSqliteTableColumn.js
│ ├── insertSqliteTableData.js
│ ├── searchSqliteTableColumn.js
│ └── searchSqliteTableData.js
│ ├── get2faSecret.js
│ ├── nlog.js
│ ├── randomInt.js
│ ├── randomPassword.js
│ └── randomString.js
├── example
├── .DS_Store
├── 2fa.png
├── add.png
├── edit.png
├── handle.png
├── list.png
├── login.png
└── register.png
└── web
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── public
├── images
│ ├── cancelSetting.png
│ └── setting.png
├── next.svg
└── vercel.svg
├── src
├── apis
│ ├── addPassword.ts
│ ├── deletePassword.ts
│ ├── editNewPassword.ts
│ ├── get2faToken.ts
│ ├── getPassword.ts
│ ├── login.ts
│ └── register.ts
├── app
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.module.css
│ └── page.tsx
├── components
│ └── loading
│ │ ├── page.module.css
│ │ └── page.tsx
├── config
│ └── config.ts
└── utils
│ └── request.ts
└── tsconfig.json
/DockerBuild.md:
--------------------------------------------------------------------------------
1 | ## 指定打包架构
2 |
3 | `docker buildx build --platform linux/amd64 -t password_next .`
4 |
5 | `docker buildx build --platform linux/arm64 -t password_next .`
6 |
7 | `docker buildx build --platform linux/arm64,linux/amd64 -t password_next .`
8 |
9 | ## 镜像标签
10 |
11 | `docker tag password_next:latest nocn/password_next:latest`
12 |
13 | `docker tag password_next:latest nocn/password_next:AMD64`
14 |
15 | `docker tag password_next:latest nocn/password_next:ARM64`
16 |
17 | ## 推送镜像到仓库
18 |
19 | `docker push nocn/password_next:latest`
20 |
21 | `docker push nocn/password_next:AMD64`
22 |
23 | `docker push nocn/password_next:ARM64`
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # 使用官方的Node.js镜像作为基础镜像
2 | FROM node:18.17.0
3 |
4 | # 设置工作目录
5 | WORKDIR /app
6 |
7 | # 复制前端代码并构建
8 | COPY web /app/web
9 | WORKDIR /app/web
10 | RUN npm install
11 |
12 | # 复制后端代码并构建
13 | COPY admin /app/admin
14 | WORKDIR /app/admin
15 | RUN npm install
16 |
17 | # 重新构建 sqlite3 模块
18 | RUN npm rebuild sqlite3
19 |
20 | # 暴露端口
21 | EXPOSE 3000
22 | EXPOSE 19899
23 |
24 | # 启动应用
25 | CMD ["sh", "-c", "cd /app/web && npm run build && npm start & cd /app/admin && node index.js"]
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 HChaoHui
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Password-Next
2 |
3 | Password-Next is a minimalist password manager designed to securely store your passwords with ease.
4 |
5 | ### Technologies Used
6 |
7 | - **Frontend**: Built using NextJS.
8 | - **Backend**: Utilizes Koa for API development and Sqlite for data storage.
9 |
10 | ### Getting Started
11 |
12 | #### Local Setup
13 |
14 | 1. **Clone the repository:**
15 | ```bash
16 | git clone https://github.com/HChaoHui/Password-Next.git
17 | ```
18 | 2. **Navigate to the web directory:**
19 | ```bash
20 | cd Password-Next/web
21 | ```
22 | 3. **Install dependencies:**
23 | ```bash
24 | npm install
25 | ```
26 | 4. **Run the development server:**
27 | ```bash
28 | npm run dev
29 | ```
30 | 5. **Open your browser and visit:**
31 | ```
32 | http://localhost:3000
33 | ```
34 | 6. **Navigate to the admin directory:**
35 | ```bash
36 | cd ../admin
37 | ```
38 | 7. **Install dependencies:**
39 | ```bash
40 | npm install
41 | ```
42 | 8. **Start the backend server:**
43 | ```bash
44 | npm run start
45 | ```
46 | 9. **API will be available at:**
47 | ```
48 | http://localhost:19899
49 | ```
50 | 10. **Default Token:**
51 | ```
52 | PasswordNext
53 | ```
54 |
55 | #### Docker Deployment
56 |
57 | 1. **Docker Address:**
58 | [nocn/password_next](https://hub.docker.com/r/nocn/password_next)
59 | 2. **Run the Docker container:**
60 | ```bash
61 | docker run -p 3000:3000 -p 19899:19899 -e NEXT_PUBLIC_API_URL=http://127.0.0.1:19899 nocn/password_next
62 | ```
63 | replace "http://127.0.0.1:19899" with your API address
64 | Map the data persistence to the 'db' directory within the admin folder.
65 |
66 | ### Demo
67 |
68 | Check out the live demo [here](https://password.next.688828.xyz/).
69 | The demo site may periodically delete data, so please do not store important information there.
70 |
71 | ### Example Screenshots
72 |
73 | 
74 | 
75 | 
76 | 
77 | 
78 | 
79 | 
80 |
81 | ### Star History
82 |
83 | [](https://star-history.com/#HChaoHui/Password-Next&Date)
84 |
85 | ### Contributing
86 |
87 | Feel free to contribute to the project by opening issues or submitting pull requests. Your feedback and contributions are highly appreciated!
88 |
89 | ### License
90 |
91 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
--------------------------------------------------------------------------------
/admin/db/password/PasswordNext.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/admin/db/password/PasswordNext.sqlite
--------------------------------------------------------------------------------
/admin/db/user/user.md:
--------------------------------------------------------------------------------
1 | ## userToken
2 |
3 | id: "INTEGER PRIMARY KEY"
4 | token: "TEXT"
5 | user_id: "INTEGER",
6 | user: "TEXT"
--------------------------------------------------------------------------------
/admin/db/user/user.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/admin/db/user/user.sqlite
--------------------------------------------------------------------------------
/admin/db/user/user_backup.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/admin/db/user/user_backup.sqlite
--------------------------------------------------------------------------------
/admin/index.js:
--------------------------------------------------------------------------------
1 | const Koa = require('koa');
2 | const cors = require('koa2-cors');
3 | const bodyParser = require('koa-bodyparser');
4 | const nlogController = require('./utils/nlog');
5 | const app = new Koa();
6 |
7 | require('dotenv').config();
8 |
9 | let corsConfig = {
10 | origin: "*",
11 | allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
12 | allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
13 | exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
14 | maxAge: 5,
15 | credentials: true,
16 | }
17 |
18 | app.use(cors(corsConfig))
19 | app.use(bodyParser());
20 | app.use(nlogController());
21 |
22 | const routers = require('./routers/main');
23 |
24 | routers.forEach((router) => {
25 | app.use(router.routes()).use(router.allowedMethods());
26 | });
27 |
28 | console.log('Server Is Start, listening On Port 19899');
29 | app.listen(19899);
--------------------------------------------------------------------------------
/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "start": "node ./index.js"
4 | },
5 | "dependencies": {
6 | "dotenv": "^16.4.5",
7 | "koa": "^2.15.3",
8 | "koa-bodyparser": "^4.4.1",
9 | "koa-router": "^12.0.1",
10 | "koa2-cors": "^2.0.6",
11 | "node-fetch": "^3.3.2",
12 | "nodemailer": "^6.9.14",
13 | "qrcode": "^1.5.3",
14 | "speakeasy": "^2.0.0",
15 | "sqlite3": "^5.1.7"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/admin/routers/main.js:
--------------------------------------------------------------------------------
1 | const password_router = require('./password/main');
2 |
3 | const routers = [
4 | ...password_router
5 | ]
6 |
7 | module.exports = routers;
--------------------------------------------------------------------------------
/admin/routers/password/addPassword.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const checkToken = require('./public/checkToken');
3 | const getSqlite = require('../../utils/database/getSqlite');
4 | const createSqlite = require('../../utils/database/createSqlite');
5 | const createSqliteTable = require('../../utils/database/createSqliteTable');
6 | const insertSqliteTableData = require('../../utils/database/insertSqliteTableData');
7 |
8 | const checkBody = async (ctx, next) => {
9 | const code400 = {
10 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
11 | };
12 | const { account, password, appName, webSite, twofa } = ctx.request.body;
13 |
14 | if (!account || !password || !appName || !webSite) {
15 | ctx.status = 400;
16 | ctx.body = code400;
17 | return;
18 | }
19 |
20 | const createSqliteInfo = await createSqlite(ctx.state.userData.user, 'password');
21 |
22 | ctx.state.nlog(createSqliteInfo);
23 |
24 | const db = getSqlite(ctx.state.userData.user, 'password');
25 |
26 | const createTableInfo = await createSqliteTable(db, 'password', {
27 | id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
28 | account: 'TEXT',
29 | password: 'TEXT',
30 | appName: 'TEXT',
31 | webSite: 'TEXT',
32 | createTime: 'TEXT',
33 | updateTime: 'TEXT',
34 | twofa: "TEXT"
35 | });
36 |
37 | ctx.state.nlog(createTableInfo);
38 |
39 | const insertDataInfo = await insertSqliteTableData(db, 'password', {
40 | account: account,
41 | password: password,
42 | appName: appName,
43 | webSite: webSite,
44 | createTime: new Date().toISOString(),
45 | updateTime: new Date().toISOString(),
46 | twofa: twofa ? twofa : ''
47 | });
48 |
49 | ctx.state.nlog(insertDataInfo);
50 |
51 | db.close();
52 |
53 | ctx.state.responseData = {
54 | status: 200,
55 | message: 'success',
56 | }
57 |
58 | await next();
59 | };
60 | const addPassword = async (ctx) => {
61 | ctx.body = ctx.state.responseData
62 | };
63 |
64 | router.post('/addPassword', checkToken, checkBody, addPassword);
65 |
66 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/deletePassword.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const checkToken = require('./public/checkToken');
3 | const getSqlite = require('../../utils/database/getSqlite');
4 | const deleteSqliteTableData = require('../../utils/database/deleteSqliteTableData');
5 |
6 | const checkBody = async (ctx, next) => {
7 | const code400 = {
8 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
9 | };
10 | const { id } = ctx.request.body;
11 |
12 | if (!id) {
13 | ctx.status = 400;
14 | ctx.body = code400;
15 | return;
16 | }
17 |
18 | const db = getSqlite(ctx.state.userData.user, 'password');
19 |
20 | const deleteDataInfo = await deleteSqliteTableData(db,'password',{id})
21 |
22 | ctx.state.nlog(deleteDataInfo);
23 |
24 | db.close();
25 |
26 | ctx.state.responseData = {
27 | status: 200,
28 | message: 'success',
29 | }
30 |
31 | await next();
32 | };
33 | const deletePassword = async (ctx) => {
34 | ctx.body = ctx.state.responseData
35 | };
36 |
37 | router.post('/deletePassword', checkToken, checkBody, deletePassword);
38 |
39 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/editPassword.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const checkToken = require('./public/checkToken');
3 | const getSqlite = require('../../utils/database/getSqlite');
4 | const changeSqliteTableData = require('../../utils/database/changeSqliteTableData');
5 |
6 | const checkBody = async (ctx, next) => {
7 | const code400 = {
8 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
9 | };
10 | const { id, account, password, appName, webSite, twofa } = ctx.request.body;
11 |
12 | if (!id || !account || !password || !appName || !webSite) {
13 | ctx.status = 400;
14 | ctx.body = code400;
15 | return;
16 | }
17 |
18 | const db = getSqlite(ctx.state.userData.user, 'password');
19 |
20 | const updateData = {
21 | account: account,
22 | password: password,
23 | appName: appName,
24 | webSite: webSite,
25 | updateTime: new Date().toISOString(),
26 | twofa: twofa ? twofa : ''
27 | }
28 |
29 | const conditions = {
30 | id: id
31 | }
32 |
33 | const updateDataInfo = await changeSqliteTableData(db, 'password', updateData, conditions);
34 |
35 | ctx.state.nlog(updateDataInfo);
36 |
37 | db.close();
38 |
39 | ctx.state.responseData = {
40 | status: 200,
41 | message: 'success',
42 | }
43 |
44 | await next();
45 | };
46 | const editPassword = async (ctx) => {
47 | ctx.body = ctx.state.responseData
48 | };
49 |
50 | router.post('/editPassword', checkToken, checkBody, editPassword);
51 |
52 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/get2faSecret.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const getSecretQRCode = require('../../utils/get2faSecret');
3 |
4 | const get2faSecret = async (ctx) => {
5 |
6 |
7 | const data = await getSecretQRCode()
8 |
9 | ctx.body = {
10 | data,
11 | status: 200,
12 | message: 'success',
13 | }
14 |
15 | };
16 |
17 | router.get('/get2faSecret', get2faSecret);
18 |
19 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/get2faToken.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const speakeasy = require('speakeasy');
3 | const checkToken = require('./public/checkToken');
4 |
5 | const checkBody = async (ctx, next) => {
6 |
7 | const code400 = {
8 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
9 | };
10 |
11 | const { secret } = ctx.request.body;
12 |
13 | if (!secret) {
14 | ctx.status = 400;
15 | ctx.body = code400;
16 | return;
17 | }
18 |
19 | await next();
20 | };
21 |
22 | const get2faToken = async (ctx) => {
23 |
24 | const { secret } = ctx.request.body;
25 |
26 | const token = speakeasy.totp({
27 | secret: secret,
28 | encoding: 'base32'
29 | });
30 |
31 | // 计算剩余时间
32 | const timeStep = 30; // 30秒为一个周期
33 | const currentTime = Math.floor(Date.now() / 1000);
34 | const remainingTime = timeStep - (currentTime % timeStep);
35 |
36 | ctx.body = {
37 | data: {
38 | token: token,
39 | remainingTime: remainingTime,
40 | },
41 | status: 200,
42 | message: 'success',
43 | }
44 |
45 | };
46 |
47 | router.post('/get2faToken', checkToken, checkBody, get2faToken);
48 |
49 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/getPassword.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const checkToken = require('./public/checkToken');
3 | const getSqlite = require('../../utils/database/getSqlite');
4 | const searchSqliteTableData = require('../../utils/database/searchSqliteTableData');
5 |
6 | const getPassword = async (ctx) => {
7 |
8 | try {
9 | const db = getSqlite(ctx.state.userData.user, 'password')
10 | const result = await searchSqliteTableData(db, 'password', {});
11 | db.close();
12 | ctx.body = {
13 | data: result,
14 | status: 200,
15 | message: 'success',
16 | }
17 | } catch (error) {
18 | ctx.body = {
19 | data: [],
20 | status: 200,
21 | message: 'success',
22 | }
23 | }
24 |
25 | };
26 |
27 | router.get('/getPassword', checkToken, getPassword);
28 |
29 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/getRandomPassword.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const randomPassword = require('../../utils/randomPassword');
3 |
4 | const getRandomPassword = async (ctx) => {
5 | const password = randomPassword(20);
6 | ctx.state.nlog('PasswordGenerateSuccess: ' + password);
7 | ctx.body = {
8 | data: randomPassword(20),
9 | status: 200,
10 | message: 'success',
11 | }
12 | };
13 |
14 | router.get('/getRandomPassword', getRandomPassword);
15 |
16 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/login.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const searchSqliteTableData = require('../../utils/database/searchSqliteTableData');
3 | const getSqlite = require('../../utils/database/getSqlite');
4 | const createSqlite = require('../../utils/database/createSqlite');
5 | const createSqliteTable = require('../../utils/database/createSqliteTable');
6 | const insertSqliteTableData = require('../../utils/database/insertSqliteTableData');
7 |
8 | const isNonEmptyStringWithoutSpaces = (param) => {
9 | return typeof param === 'string' && param.replace(/\s+/g, '').length > 0;
10 | }
11 |
12 | const checkBody = async (ctx, next) => {
13 | const code400 = {
14 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
15 | };
16 | const { token } = ctx.request.body;
17 |
18 |
19 |
20 | if (!isNonEmptyStringWithoutSpaces(token)) {
21 | ctx.status = 400;
22 | ctx.body = code400;
23 | return;
24 | }
25 |
26 | ctx.state.token = token;
27 |
28 | await next();
29 | };
30 |
31 | const authToken = async (ctx, next) => {
32 |
33 |
34 | const code401 = {
35 | msg: 'Access denied. Please verify your identity to proceed.'
36 | };
37 |
38 | const db = getSqlite('user', 'user')
39 |
40 | const result = await searchSqliteTableData(db, 'userToken', { token: ctx.state.token });
41 |
42 | if (result.length <= 0) {
43 | ctx.status = 400;
44 | ctx.body = code401;
45 | return;
46 | }
47 |
48 | ctx.state.responseData = result[0];
49 |
50 | await next();
51 |
52 | };
53 |
54 | const login = async (ctx) => {
55 | ctx.body = {
56 | data: ctx.state.responseData,
57 | status: 200,
58 | message: 'success',
59 | }
60 | };
61 |
62 | router.post('/login', checkBody, authToken, login);
63 |
64 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/main.js:
--------------------------------------------------------------------------------
1 | const addpassword_router = require('./addPassword');
2 | const getrandompassword_router = require('./getRandomPassword');
3 | const login_router = require('./login');
4 | const get2fasecret_router = require('./get2faSecret');
5 | const get2fatoken_router = require('./get2faToken');
6 | const getpassword_router = require('./getPassword');
7 | const deletepassword_router = require('./deletePassword');
8 | const editpassword_router = require('./editPassword');
9 | const register_router = require('./register');
10 |
11 | const routers = [
12 | addpassword_router,
13 | getrandompassword_router,
14 | login_router,
15 | get2fasecret_router,
16 | get2fatoken_router,
17 | getpassword_router,
18 | deletepassword_router,
19 | editpassword_router,
20 | register_router
21 | ];
22 |
23 | module.exports = routers;
--------------------------------------------------------------------------------
/admin/routers/password/public/checkToken.js:
--------------------------------------------------------------------------------
1 | const getSqlite = require('../../../utils/database/getSqlite');
2 | const searchSqliteTableData = require('../../../utils/database/searchSqliteTableData');
3 |
4 | const checkToken = async (ctx, next) => {
5 |
6 | const code401 = {
7 | msg: 'Access denied. Please verify your identity to proceed.'
8 | };
9 | const authHeader = ctx.headers['authorization'];
10 |
11 | if (!authHeader || !authHeader.startsWith('Bearer ')) {
12 | ctx.status = 401;
13 | ctx.body = code401;
14 | return;
15 | }
16 |
17 | ctx.state.token = authHeader.substring(7);
18 |
19 | const db = getSqlite('user', 'user')
20 |
21 | const result = await searchSqliteTableData(db, 'userToken', { token: ctx.state.token });
22 |
23 | db.close();
24 |
25 | if (result.length <= 0) {
26 | ctx.status = 401;
27 | ctx.body = code401;
28 | return;
29 | }
30 |
31 | ctx.state.userData = result[0];
32 |
33 | ctx.state.nlog('LoginUser: ' + ctx.state.userData.user);
34 |
35 | await next();
36 | };
37 |
38 | module.exports = checkToken;
--------------------------------------------------------------------------------
/admin/routers/password/register.js:
--------------------------------------------------------------------------------
1 | const router = require('./router');
2 | const searchSqliteTableData = require('../../utils/database/searchSqliteTableData');
3 | const insertSqliteTableData = require('../../utils/database/insertSqliteTableData');
4 | const getSqlite = require('../../utils/database/getSqlite');
5 | const getRandomPassword = require('../../utils/randomPassword');
6 |
7 | const isNonEmptyStringWithoutSpaces = (param) => {
8 | return typeof param === 'string' && param.replace(/\s+/g, '').length > 0;
9 | }
10 |
11 | const checkBody = async (ctx, next) => {
12 | const code400 = {
13 | msg: 'The request was unsuccessful. Please ensure that all data is correct and error-free.'
14 | };
15 | const { user } = ctx.request.body;
16 |
17 | if (!isNonEmptyStringWithoutSpaces(user)) {
18 | ctx.status = 400;
19 | ctx.body = code400;
20 | return;
21 | }
22 |
23 | ctx.state.user = user;
24 |
25 | await next();
26 | };
27 |
28 | const authUser = async (ctx, next) => {
29 |
30 |
31 | const code500 = {
32 | msg: 'The username already exists. Please try another one.'
33 | };
34 |
35 | const db = getSqlite('user', 'user')
36 |
37 | const result = await searchSqliteTableData(db, 'userToken', { user: ctx.state.user });
38 |
39 | if (result.length > 0) {
40 | ctx.status = 500;
41 | ctx.body = code500;
42 | return;
43 | }
44 |
45 | const token = getRandomPassword(30);
46 |
47 | const insertInfo = await insertSqliteTableData(db, 'userToken', { user: ctx.state.user, token });
48 |
49 | ctx.state.nlog(insertInfo)
50 |
51 | ctx.state.responseData = {
52 | token,
53 | user: ctx.state.user,
54 | };
55 |
56 | await next();
57 |
58 | };
59 |
60 | const register = async (ctx) => {
61 | ctx.body = {
62 | data: ctx.state.responseData,
63 | status: 200,
64 | message: 'success',
65 | }
66 | };
67 |
68 | router.post('/register', checkBody, authUser, register);
69 |
70 | module.exports = router;
--------------------------------------------------------------------------------
/admin/routers/password/router.js:
--------------------------------------------------------------------------------
1 | const Router = require('koa-router');
2 | const path = require('path');
3 | const router = new Router();
4 | const currentDirectory = path.basename(__dirname);
5 | router.prefix('/' + currentDirectory)
6 |
7 | module.exports = router
--------------------------------------------------------------------------------
/admin/utils/database/changeSqliteTableColumn.js:
--------------------------------------------------------------------------------
1 | const changeSqliteTableColumn = (db, tableName, oldColumn, newColumn, newColumnType) => {
2 | return new Promise((resolve, reject) => {
3 | db.serialize(async () => {
4 | try {
5 | const tableInfo = await new Promise((resolve, reject) => {
6 | db.all(`PRAGMA table_info(${tableName})`, (err, rows) => {
7 | if (err) {
8 | reject(err);
9 | } else {
10 | resolve(rows);
11 | }
12 | });
13 | });
14 |
15 | const columns = tableInfo.map(info => info.name);
16 |
17 | if (!columns.includes(oldColumn)) {
18 | throw new Error(`Column ${oldColumn} does not exist in table ${tableName}`);
19 | }
20 |
21 | const newColumnsWithTypes = tableInfo.map(info => {
22 | if (info.name === oldColumn) {
23 | return `${newColumn} ${newColumnType}`;
24 | }
25 | return `${info.name} ${info.type}`;
26 | }).join(', ');
27 |
28 | const newColumns = tableInfo.map(info => {
29 | if (info.name === oldColumn) {
30 | return `${newColumn}`;
31 | }
32 | return `${info.name}`;
33 | }).join(', ');
34 |
35 | const tempTable = `${tableName}_temp`;
36 | await new Promise((resolve, reject) => {
37 | db.run(`ALTER TABLE ${tableName} RENAME TO ${tempTable}`, (err) => {
38 | if (err) {
39 | reject(err);
40 | } else {
41 | resolve();
42 | }
43 | });
44 | });
45 |
46 | await new Promise((resolve, reject) => {
47 | db.run(`CREATE TABLE ${tableName} (${newColumnsWithTypes})`, (err) => {
48 | if (err) {
49 | reject(err);
50 | } else {
51 | resolve();
52 | }
53 | });
54 | });
55 |
56 | await new Promise((resolve, reject) => {
57 | db.run(`INSERT INTO ${tableName} (${newColumns}) SELECT ${newColumns} FROM ${tempTable}`, (err) => {
58 | if (err) {
59 | reject(err);
60 | } else {
61 | resolve();
62 | }
63 | });
64 | });
65 |
66 | await new Promise((resolve, reject) => {
67 | db.run(`DROP TABLE ${tempTable}`, (err) => {
68 | if (err) {
69 | reject(err);
70 | } else {
71 | resolve();
72 | }
73 | });
74 | });
75 |
76 | resolve(`Column ${oldColumn} modified to ${newColumn} in table ${tableName}`);
77 | } catch (err) {
78 | reject(err);
79 | }
80 | });
81 | });
82 | }
83 |
84 | // 修改Admin数据库password的表website字段为webSite
85 | // (async () => {
86 | // const db = require('./getSqlite')('Admin', 'password');
87 | // console.log(await changeSqliteTableColumn(db, 'password','website','webSite','TEXT'));
88 | // })()
89 |
90 | module.exports = changeSqliteTableColumn;
91 |
--------------------------------------------------------------------------------
/admin/utils/database/changeSqliteTableData.js:
--------------------------------------------------------------------------------
1 | const changeSqliteTableData = (db, tableName, updates, conditions) => {
2 | return new Promise((resolve, reject) => {
3 | const setClause = Object.keys(updates).map(key => `${key} = ?`).join(', ');
4 | const whereClause = Object.keys(conditions).map(key => `${key} = ?`).join(' AND ');
5 | const values = [...Object.values(updates), ...Object.values(conditions)];
6 | const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`;
7 | db.run(sql, values, function (err) {
8 | if (err) {
9 | reject(err);
10 | db.close();
11 | } else {
12 | resolve(this.changes);
13 | }
14 | });
15 | });
16 | }
17 |
18 | module.exports = changeSqliteTableData;
19 |
--------------------------------------------------------------------------------
/admin/utils/database/createSqlite.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const sqlite3 = require('sqlite3').verbose();
3 | const path = require('path');
4 | const dbDir = path.join(__dirname, '../../db/')
5 |
6 | const createSqlite = (baseName, baseDir) => {
7 |
8 | return new Promise((resolve) => {
9 | const dbPath = path.join(dbDir, baseDir);
10 |
11 | if (!fs.existsSync(dbPath)) {
12 | fs.mkdirSync(dbPath);
13 | }
14 |
15 | if (fs.existsSync(path.join(dbPath, baseName + '.sqlite'))) {
16 | resolve('Database Already Exists')
17 | }
18 |
19 | new sqlite3.Database(path.join(dbPath, baseName + '.sqlite'));
20 |
21 | // 用于延迟执行 不然创建完成后立即获取不到Sqlite文件
22 | setTimeout(() => {
23 | resolve('Database Created')
24 | }, 1000)
25 | })
26 |
27 | }
28 |
29 | module.exports = createSqlite;
30 |
--------------------------------------------------------------------------------
/admin/utils/database/createSqliteTable.js:
--------------------------------------------------------------------------------
1 | const createSqliteTable = (db, tableName, columns) => {
2 | return new Promise((resolve, reject) => {
3 | let columnsDef = Object.entries(columns).map(([key, value]) => `${key} ${value}`).join(", ");
4 | const createTableSQL = `CREATE TABLE IF NOT EXISTS ${tableName} (${columnsDef})`;
5 | db.run(createTableSQL, (err) => {
6 | if (err) {
7 | reject("Error creating table:", err.message);
8 | }
9 | resolve(`${tableName} table created successfully.`);
10 | });
11 | });
12 | }
13 |
14 | // 创建user数据库userToken表的数据
15 | // (async () => {
16 | // const db = require('./getSqlite')('user', 'user');
17 | // console.log(await createSqliteTable(db, 'userToken', {
18 | // id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
19 | // token: 'TEXT',
20 | // user_id: "INTEGER",
21 | // user: "TEXT",
22 | // }));
23 | // })()
24 |
25 | module.exports = createSqliteTable;
26 |
--------------------------------------------------------------------------------
/admin/utils/database/deleteSqliteTableColumn.js:
--------------------------------------------------------------------------------
1 | const deleteSqliteTableColumn = (db, tableName, column) => {
2 | return new Promise((resolve, reject) => {
3 | db.all(`PRAGMA table_info(${tableName});`, [], (err, columns) => {
4 | if (err) {
5 | return reject(err);
6 | }
7 |
8 | if (!columns.some(col => col.name === column)) {
9 | return reject(new Error(`Column ${column} does not exist in table ${tableName}`));
10 | }
11 |
12 | const newColumns = columns.filter(col => col.name !== column).map(col => `${col.name} ${col.type}`).join(', ');
13 |
14 | const newColumnNames = columns.filter(col => col.name !== column).map(col => col.name).join(', ');
15 |
16 | const createTempTableSQL = `CREATE TABLE ${tableName}_temp (${newColumns});`;
17 | const copyDataSQL = `INSERT INTO ${tableName}_temp SELECT ${newColumnNames} FROM ${tableName};`;
18 | const dropOldTableSQL = `DROP TABLE ${tableName};`;
19 | const renameTempTableSQL = `ALTER TABLE ${tableName}_temp RENAME TO ${tableName};`;
20 |
21 | db.serialize(() => {
22 | db.run("BEGIN TRANSACTION;");
23 | db.run(createTempTableSQL);
24 | db.run(copyDataSQL);
25 | db.run(dropOldTableSQL);
26 | db.run(renameTempTableSQL);
27 | db.run("COMMIT;", (err) => {
28 | if (err) {
29 | db.run("ROLLBACK;");
30 | return reject(err);
31 | }
32 | resolve(`Column ${column} removed from ${tableName} table`);
33 | });
34 | });
35 | });
36 | });
37 | }
38 |
39 | module.exports = deleteSqliteTableColumn;
40 |
--------------------------------------------------------------------------------
/admin/utils/database/deleteSqliteTableData.js:
--------------------------------------------------------------------------------
1 | const deleteSqliteTableData = (db, tableName, conditions) => {
2 | return new Promise((resolve, reject) => {
3 | const whereClause = Object.keys(conditions).map(key => `${key} = ?`).join(' AND ');
4 | const values = Object.values(conditions);
5 | const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`;
6 | db.run(sql, values, function (err) {
7 | if (err) {
8 | reject(err);
9 | } else {
10 | resolve(this.changes);
11 | }
12 | });
13 | });
14 | }
15 |
16 | module.exports = deleteSqliteTableData;
17 |
--------------------------------------------------------------------------------
/admin/utils/database/getSqlite.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const sqlite3 = require('sqlite3').verbose();
3 | const path = require('path');
4 | const dbDir = path.join(__dirname, '../../db/')
5 |
6 | const getSqlite = (baseName, baseDir) => {
7 |
8 | const dbPath = path.join(dbDir, baseDir);
9 | const basePath = path.join(dbPath, baseName + '.sqlite');
10 |
11 | if (!fs.existsSync(basePath)) {
12 | throw new Error('Database Not Found.');
13 | }
14 |
15 | const db = new sqlite3.Database(basePath);
16 |
17 | return db
18 |
19 | }
20 |
21 | module.exports = getSqlite;
22 |
--------------------------------------------------------------------------------
/admin/utils/database/getSqliteAllTable.js:
--------------------------------------------------------------------------------
1 | const getSqliteAllTable = async (db) => {
2 | return new Promise((resolve, reject) => {
3 | const getSqliteAllTableSQL = `SELECT name FROM sqlite_master WHERE type='table'`;
4 | db.all(getSqliteAllTableSQL, (err, rows) => {
5 | if (err) {
6 | reject(err);
7 | }
8 |
9 | resolve(rows);
10 | });
11 | })
12 | }
13 |
14 | // 查询Admin数据库的表
15 | // (async () => {
16 | // const db = require('./getSqlite')('Admin', 'password');
17 | // console.log(await getSqliteAllTable(db));
18 | // })()
19 |
20 | module.exports = getSqliteAllTable;
21 |
--------------------------------------------------------------------------------
/admin/utils/database/insertSqliteTableColumn.js:
--------------------------------------------------------------------------------
1 | const insertSqliteTableColumn = (db, tableName, column, columnType) => {
2 | return new Promise((resolve, reject) => {
3 | const sql = `ALTER TABLE ${tableName} ADD COLUMN ${column} ${columnType}`;
4 | db.run(sql, function (err) {
5 | if (err) {
6 | reject(err);
7 | } else {
8 | resolve(`Column ${column} added to ${tableName} table`);
9 | }
10 | });
11 | });
12 | }
13 |
14 | // 向Admin数据库password的表中插入twofa字段类型为TEXT
15 | // (async () => {
16 | // const db = require('./getSqlite')('Admin', 'password');
17 | // console.log(await insertSqliteTableColumn(db, 'password','twofa','TEXT'));
18 | // })()
19 |
20 | module.exports = insertSqliteTableColumn;
21 |
--------------------------------------------------------------------------------
/admin/utils/database/insertSqliteTableData.js:
--------------------------------------------------------------------------------
1 | const insertSqliteTableData = (db, tableName, data) => {
2 | return new Promise((resolve, reject) => {
3 | const columns = Object.keys(data).join(', ');
4 | const placeholders = Object.keys(data).map(key => '?').join(', ');
5 | const values = Object.values(data);
6 | const sql = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`;
7 | db.run(sql, values, function (err) {
8 | if (err) {
9 | reject(err);
10 | } else {
11 | resolve("Insert Data Success, LastID: " + this.lastID);
12 | }
13 | });
14 | });
15 | }
16 |
17 | // (async () => {
18 | // const db = require('./getSqlite')('user', 'user');
19 | // console.log(await insertSqliteTableData(db, 'userToken', {
20 | // token: 'PasswordNext',
21 | // user_id: "1",
22 | // user: "PasswordNext",
23 | // }));
24 | // })()
25 |
26 | module.exports = insertSqliteTableData;
27 |
--------------------------------------------------------------------------------
/admin/utils/database/searchSqliteTableColumn.js:
--------------------------------------------------------------------------------
1 | const searchSqliteTableColumn = (db, tableName) => {
2 | return new Promise((resolve, reject) => {
3 | const sql = `PRAGMA table_info(${tableName})`;
4 | db.all(sql, [], (err, rows) => {
5 | if (err) {
6 | reject(err);
7 | } else {
8 | resolve(rows);
9 | }
10 | });
11 | });
12 | }
13 |
14 | module.exports = searchSqliteTableColumn;
15 |
--------------------------------------------------------------------------------
/admin/utils/database/searchSqliteTableData.js:
--------------------------------------------------------------------------------
1 | const searchSqliteTableData = (db, tableName, conditions = {}, useFuzzySearch = false) => {
2 | return new Promise((resolve, reject) => {
3 | let whereClause = '';
4 | let values = [];
5 |
6 | if (useFuzzySearch) {
7 | whereClause = Object.keys(conditions).map(key => `${key} LIKE ?`).join(' AND ');
8 | values = Object.values(conditions).map(value => `%${value}%`);
9 | } else {
10 | whereClause = Object.keys(conditions).map(key => `${key} = ?`).join(' AND ');
11 | values = Object.values(conditions);
12 | }
13 |
14 | const sql = `SELECT * FROM ${tableName}` + (whereClause ? ` WHERE ${whereClause}` : '');
15 | db.all(sql, values, (err, rows) => {
16 | if (err) {
17 | reject(err);
18 | } else {
19 | resolve(rows);
20 | }
21 | });
22 | });
23 | }
24 |
25 | // 查询user数据库userToken表的数据
26 | // (async () => {
27 | // const db = require('./getSqlite')('user', 'user');
28 | // console.log(await searchSqliteTableData(db, 'userToken', { }));
29 | // })()
30 |
31 | // 查询Admin数据库password表的数据
32 | // (async () => {
33 | // const db = require('./getSqlite')('Admin', 'password');
34 | // console.log(await searchSqliteTableData(db, 'password', { }));
35 | // })()
36 |
37 | module.exports = searchSqliteTableData;
38 |
--------------------------------------------------------------------------------
/admin/utils/get2faSecret.js:
--------------------------------------------------------------------------------
1 | const speakeasy = require('speakeasy');
2 | const QRCode = require('qrcode');
3 |
4 | const get2faSecretQRCode = (otpauth_url) => {
5 |
6 | return new Promise((resolve, reject) => {
7 | QRCode.toDataURL(otpauth_url, (err, data_url) => {
8 | if (err) {
9 | reject(err);
10 | }
11 | resolve(data_url);
12 | });
13 | });
14 |
15 | }
16 |
17 | const getSecretQRCode = async () => {
18 |
19 | const secret = speakeasy.generateSecret({ length: 20 });
20 |
21 | const QRCode = await get2faSecretQRCode(secret.otpauth_url);
22 |
23 | return {
24 | secret: secret.base32,
25 | QRCode: QRCode,
26 | }
27 |
28 | }
29 |
30 |
31 | module.exports = getSecretQRCode;
--------------------------------------------------------------------------------
/admin/utils/nlog.js:
--------------------------------------------------------------------------------
1 | const nlog = (message) => {
2 | const cstTimestamp = new Date(new Date().getTime() + 8 * 60 * 60 * 1000);
3 | console.log(`[${cstTimestamp.toISOString()}] [INFO] ${message}`);
4 | }
5 |
6 | const nlogController = () => async (ctx, next) => { ctx.state.nlog = nlog; await next(); }
7 |
8 | module.exports = nlogController;
--------------------------------------------------------------------------------
/admin/utils/randomInt.js:
--------------------------------------------------------------------------------
1 | const getRandomInt = (max) => {
2 | return Math.floor(Math.random() * max);
3 | }
4 |
5 | module.exports = getRandomInt
--------------------------------------------------------------------------------
/admin/utils/randomPassword.js:
--------------------------------------------------------------------------------
1 | const getRandomPassword = (length) => {
2 |
3 | if (!length || length - 6 < 2) {
4 | throw new Error("Length must be greater than 8");
5 | }
6 |
7 | const numLowercase = length - 6;
8 | const lowerCharset = "abcdefghijklmnopqrstuvwxyz";
9 | const upperCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
10 | const numberCharset = "0123456789";
11 | const specialCharset = "!@#$%^&*()_+[]{}|;:,.<>?";
12 | const allCharset = lowerCharset + upperCharset + numberCharset + specialCharset;
13 |
14 | let password = "";
15 |
16 | for (let i = 0; i < 2; i++) {
17 | password += upperCharset[Math.floor(Math.random() * upperCharset.length)];
18 | }
19 |
20 | for (let i = 0; i < numLowercase; i++) {
21 | password += lowerCharset[Math.floor(Math.random() * lowerCharset.length)];
22 | }
23 |
24 | for (let i = 0; i < 2; i++) {
25 | password += numberCharset[Math.floor(Math.random() * numberCharset.length)];
26 | }
27 |
28 | for (let i = 0; i < 2; i++) {
29 | password += specialCharset[Math.floor(Math.random() * specialCharset.length)];
30 | }
31 |
32 | for (let i = password.length; i < length; i++) {
33 | password += allCharset[Math.floor(Math.random() * allCharset.length)];
34 | }
35 |
36 | password = password.split('').sort(() => 0.5 - Math.random()).join('');
37 |
38 | return password;
39 | }
40 |
41 |
42 | module.exports = getRandomPassword;
--------------------------------------------------------------------------------
/admin/utils/randomString.js:
--------------------------------------------------------------------------------
1 | const getRandomString = (length) => {
2 | let result = '';
3 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
4 | const charactersLength = characters.length;
5 | for (let i = 0; i < length; i++) {
6 | result += characters.charAt(Math.floor(Math.random() * charactersLength));
7 | if (i === 0) {
8 | result += getRandomInt(10);
9 | }
10 | }
11 | return result;
12 | }
13 |
14 | module.exports = getRandomString;
--------------------------------------------------------------------------------
/example/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/.DS_Store
--------------------------------------------------------------------------------
/example/2fa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/2fa.png
--------------------------------------------------------------------------------
/example/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/add.png
--------------------------------------------------------------------------------
/example/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/edit.png
--------------------------------------------------------------------------------
/example/handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/handle.png
--------------------------------------------------------------------------------
/example/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/list.png
--------------------------------------------------------------------------------
/example/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/login.png
--------------------------------------------------------------------------------
/example/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/example/register.png
--------------------------------------------------------------------------------
/web/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/web/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | env: {
4 | NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:19899',
5 | }
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "password_app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "axios": "^1.7.2",
13 | "next": "14.2.4",
14 | "react": "^18",
15 | "react-dom": "^18"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20",
19 | "@types/react": "^18",
20 | "@types/react-dom": "^18",
21 | "typescript": "^5"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/web/public/images/cancelSetting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/web/public/images/cancelSetting.png
--------------------------------------------------------------------------------
/web/public/images/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/web/public/images/setting.png
--------------------------------------------------------------------------------
/web/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/src/apis/addPassword.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface AddPasswordRequest {
4 | account: string;
5 | password: string;
6 | appName: string;
7 | webSite: string;
8 | twofa: string;
9 | }
10 |
11 | const addPassword = async (requestData: AddPasswordRequest) => {
12 |
13 | const response = await Post("/password/addPassword", requestData);
14 |
15 | if (response.status !== 200) {
16 | throw new Error("AddPassword Fail, Please Contact Author!");
17 | }
18 |
19 | const data = response.data;
20 |
21 | return data
22 |
23 | };
24 |
25 | export { addPassword };
--------------------------------------------------------------------------------
/web/src/apis/deletePassword.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface DeletePasswordRequest {
4 | id: string;
5 | }
6 |
7 | const deletePassword = async (requestData: DeletePasswordRequest) => {
8 |
9 | const response = await Post("/password/deletePassword", requestData);
10 |
11 | if (response.status !== 200) {
12 | throw new Error("DeletePassword Fail, Please Contact Author!");
13 | }
14 |
15 | const data = response.data;
16 |
17 | return data
18 |
19 | };
20 |
21 | export { deletePassword };
--------------------------------------------------------------------------------
/web/src/apis/editNewPassword.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface EditNewPasswordRequest {
4 | id: string;
5 | account: string;
6 | password: string;
7 | appName: string;
8 | webSite: string;
9 | twofa: string;
10 | }
11 |
12 | const editNewPassword = async (requestData: EditNewPasswordRequest) => {
13 |
14 | const response = await Post("/password/editPassword", requestData);
15 |
16 | if (response.status !== 200) {
17 | throw new Error("EditNewPassword Fail, Please Contact Author!");
18 | }
19 |
20 | const data = response.data;
21 |
22 | return data
23 |
24 | };
25 |
26 | export { editNewPassword };
--------------------------------------------------------------------------------
/web/src/apis/get2faToken.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface Get2faTokenRequest {
4 | secret: string;
5 | }
6 |
7 | const get2faToken = async (requestData: Get2faTokenRequest) => {
8 |
9 |
10 | const response = await Post("/password/get2faToken", requestData);
11 |
12 |
13 | if (response.status !== 200) {
14 | throw new Error("Get2faToken Fail, Please Contact Author!");
15 | }
16 |
17 | const data = response.data;
18 |
19 | return data
20 |
21 | };
22 |
23 | export { get2faToken };
--------------------------------------------------------------------------------
/web/src/apis/getPassword.ts:
--------------------------------------------------------------------------------
1 | import { Get } from "@/utils/request";
2 |
3 | const getPassword = async () => {
4 |
5 | const response = await Get("/password/getPassword", {});
6 |
7 | if (response.status !== 200) {
8 | throw new Error("GetPassword Fail, Please Contact Author!");
9 | }
10 |
11 | const data = response.data;
12 |
13 | return data
14 |
15 | };
16 |
17 | export { getPassword };
--------------------------------------------------------------------------------
/web/src/apis/login.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface LoginRequest {
4 | token: string;
5 | }
6 |
7 | const login = async (requestData: LoginRequest) => {
8 |
9 |
10 | const response = await Post("/password/login", requestData);
11 |
12 |
13 | if (response.status !== 200) {
14 | throw new Error("Login Fail, Please Contact Author!");
15 | }
16 |
17 | const data = response.data;
18 |
19 | return data
20 |
21 | };
22 |
23 | export { login };
--------------------------------------------------------------------------------
/web/src/apis/register.ts:
--------------------------------------------------------------------------------
1 | import { Post } from "@/utils/request";
2 |
3 | interface RegisterRequest {
4 | user: string;
5 | }
6 |
7 | const register = async (requestData: RegisterRequest) => {
8 |
9 |
10 | const response = await Post("/password/register", requestData);
11 |
12 |
13 | if (response.status !== 200) {
14 | throw new Error("Register Fail, Please Contact Author!");
15 | }
16 |
17 | const data = response.data;
18 |
19 | return data
20 |
21 | };
22 |
23 | export { register };
--------------------------------------------------------------------------------
/web/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HChaoHui/Password-Next/60c6acfec86d524bd1a533819a27cc6440a28a0d/web/src/app/favicon.ico
--------------------------------------------------------------------------------
/web/src/app/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --max-width: 1100px;
3 | --border-radius: 12px;
4 | --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
5 | "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
6 | "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
7 |
8 | --foreground-rgb: 0, 0, 0;
9 | --background-start-rgb: 214, 219, 220;
10 | --background-end-rgb: 255, 255, 255;
11 |
12 | --primary-glow: conic-gradient(
13 | from 180deg at 50% 50%,
14 | #16abff33 0deg,
15 | #0885ff33 55deg,
16 | #54d6ff33 120deg,
17 | #0071ff33 160deg,
18 | transparent 360deg
19 | );
20 | --secondary-glow: radial-gradient(
21 | rgba(255, 255, 255, 1),
22 | rgba(255, 255, 255, 0)
23 | );
24 |
25 | --tile-start-rgb: 239, 245, 249;
26 | --tile-end-rgb: 228, 232, 233;
27 | --tile-border: conic-gradient(
28 | #00000080,
29 | #00000040,
30 | #00000030,
31 | #00000020,
32 | #00000010,
33 | #00000010,
34 | #00000080
35 | );
36 |
37 | --callout-rgb: 238, 240, 241;
38 | --callout-border-rgb: 172, 175, 176;
39 | --card-rgb: 180, 185, 188;
40 | --card-border-rgb: 131, 134, 135;
41 | }
42 |
43 | @media (prefers-color-scheme: dark) {
44 | :root {
45 | --foreground-rgb: 255, 255, 255;
46 | --background-start-rgb: 0, 0, 0;
47 | --background-end-rgb: 0, 0, 0;
48 |
49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50 | --secondary-glow: linear-gradient(
51 | to bottom right,
52 | rgba(1, 65, 255, 0),
53 | rgba(1, 65, 255, 0),
54 | rgba(1, 65, 255, 0.3)
55 | );
56 |
57 | --tile-start-rgb: 2, 13, 46;
58 | --tile-end-rgb: 2, 5, 19;
59 | --tile-border: conic-gradient(
60 | #ffffff80,
61 | #ffffff40,
62 | #ffffff30,
63 | #ffffff20,
64 | #ffffff10,
65 | #ffffff10,
66 | #ffffff80
67 | );
68 |
69 | --callout-rgb: 20, 20, 20;
70 | --callout-border-rgb: 108, 108, 108;
71 | --card-rgb: 100, 100, 100;
72 | --card-border-rgb: 200, 200, 200;
73 | }
74 | }
75 |
76 | * {
77 | box-sizing: border-box;
78 | padding: 0;
79 | margin: 0;
80 | }
81 |
82 | html,
83 | body {
84 | max-width: 100vw;
85 | overflow-x: hidden;
86 | }
87 |
88 | body {
89 | color: rgb(var(--foreground-rgb));
90 | background: linear-gradient(
91 | to bottom,
92 | transparent,
93 | rgb(var(--background-end-rgb))
94 | )
95 | rgb(var(--background-start-rgb));
96 | }
97 |
98 | a {
99 | color: inherit;
100 | text-decoration: none;
101 | }
102 |
103 | @media (prefers-color-scheme: dark) {
104 | html {
105 | color-scheme: dark;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/web/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const inter = Inter({ subsets: ["latin"] });
6 |
7 | export const metadata: Metadata = {
8 | title: "Password Next",
9 | description: "Personal Password Repository",
10 | };
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode;
16 | }>) {
17 | return (
18 |
19 |
{children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/web/src/app/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | max-width: 100%;
46 | width: var(--max-width);
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | min-width: 300px
56 | }
57 |
58 | .card span {
59 | display: inline-block;
60 | transition: transform 200ms;
61 | }
62 |
63 | .card h2 {
64 | font-weight: 600;
65 | margin-bottom: 0.7rem;
66 | }
67 |
68 | .card p {
69 | margin: 0;
70 | opacity: 0.6;
71 | font-size: 0.9rem;
72 | line-height: 1.5;
73 | max-width: 30ch;
74 | text-wrap: balance;
75 | }
76 |
77 | .center {
78 | width: 100%;
79 | display: flex;
80 | justify-content: center;
81 | align-items: center;
82 | position: relative;
83 | max-width: var(--max-width);
84 | }
85 |
86 | .center::before {
87 | background: var(--secondary-glow);
88 | border-radius: 50%;
89 | width: 480px;
90 | height: 360px;
91 | margin-left: -400px;
92 | }
93 |
94 | .center::after {
95 | background: var(--primary-glow);
96 | width: 240px;
97 | height: 180px;
98 | z-index: -1;
99 | }
100 |
101 | .center::before,
102 | .center::after {
103 | content: "";
104 | left: 50%;
105 | position: absolute;
106 | filter: blur(45px);
107 | transform: translateZ(0);
108 | }
109 |
110 | .logo {
111 | position: relative;
112 | }
113 |
114 | .loginForm {
115 | width: 600px;
116 | position: relative;
117 | display: flex;
118 | flex-wrap: wrap;
119 | justify-content: center;
120 | }
121 |
122 | .loginText {
123 | width: 100%;
124 | height: 200px;
125 | padding: 10px;
126 | resize: none;
127 | outline: none;
128 | border-radius: 6px;
129 | font-family: var(--font-mono);
130 | }
131 |
132 | .loginButton {
133 | width: 100%;
134 | padding: 16px 0px;
135 | margin: 20px auto;
136 | border: none;
137 | border-radius: 6px;
138 | background-color: #1e293b;
139 | color: #FFFFFF;
140 | cursor: pointer;
141 | font-family: var(--font-mono);
142 | }
143 |
144 | .loginTips {
145 | width: 100%;
146 | height: 20px;
147 | text-align: left;
148 | font-size: 12px;
149 | font-weight: bold;
150 | color: red;
151 | }
152 |
153 | .passwordList {
154 | position: relative;
155 | display: flex;
156 | flex-wrap: wrap;
157 | font-family: var(--font-mono);
158 | margin: 10px auto;
159 | justify-content: space-between;
160 | flex: 1;
161 | }
162 |
163 | .password {
164 | width: 49%;
165 | margin: 0;
166 | padding: 20px;
167 | background-color: rgba(var(--callout-rgb), 0.5);
168 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
169 | border-radius: var(--border-radius);
170 | cursor: pointer;
171 | margin: 10px 0px 0px 0px;
172 | min-width: 400px;
173 | position: relative;
174 | overflow: hidden;
175 | }
176 |
177 | .passwordOption {
178 | margin: 0px 10px;
179 | display: flex;
180 | align-items: center;
181 | line-height: 30px;
182 | }
183 |
184 | .passwordLabel {
185 | width: 100px;
186 | font-weight: bold;
187 | }
188 |
189 | .passwordHref {
190 | text-decoration: underline;
191 | }
192 |
193 | .setting {
194 | position: absolute;
195 | top: 0px;
196 | right: 0px;
197 | display: flex;
198 | padding: 10px;
199 | }
200 |
201 | @keyframes topInFromSlide {
202 | from {
203 | transform: translateY(100%);
204 | }
205 |
206 | to {
207 | transform: translateY(0);
208 | }
209 | }
210 |
211 | .newPassword {
212 | width: 100%;
213 | height: auto;
214 | font-family: var(--font-mono);
215 | padding: 20px;
216 | position: absolute;
217 | /* animation: topInFromSlide 0.7s ease-in-out forwards; */
218 | }
219 |
220 | .newPasswordInput {
221 | display: flex;
222 | line-height: 40px;
223 | align-items: center;
224 | justify-content: center;
225 | margin: 0 auto;
226 | margin-bottom: 10px;
227 | }
228 |
229 | .newPasswordInput p {
230 | width: 90px;
231 | text-align: right;
232 | margin-right: 10px;
233 | font-weight: bold;
234 | }
235 |
236 | .newPasswordInput input {
237 | width: 70%;
238 | height: 40px;
239 | padding: 10px;
240 | outline: none;
241 | font-family: var(--font-mono);
242 | border: 1px solid #999;
243 | border-radius: 6px;
244 | }
245 |
246 | .newPasswordOperation {
247 | display: flex;
248 | line-height: 30px;
249 | align-items: center;
250 | justify-content: center;
251 | margin: 60px auto;
252 | margin-bottom: 0px;
253 | }
254 |
255 | .newPasswordOperation p {
256 | margin: 0px 60px;
257 | }
258 |
259 | @keyframes slideInFromTop {
260 | from {
261 | transform: translateY(-100%);
262 | }
263 |
264 | to {
265 | transform: translateY(0);
266 | }
267 | }
268 |
269 | .settingPage {
270 | position: absolute;
271 | width: 100%;
272 | height: 100%;
273 | top: 0;
274 | left: 0;
275 | background-color: rgba(var(--callout-rgb), 1);
276 | animation: slideInFromTop 0.4s ease-in-out forwards;
277 | padding: 20px;
278 | }
279 |
280 | .settingPageOperationList {
281 | display: flex;
282 | }
283 |
284 | .settingPageOperation {
285 | text-decoration: underline;
286 | margin-right: 10px;
287 | cursor: pointer;
288 | user-select: none;
289 | }
290 |
291 | .addNewPassword {
292 | text-decoration: underline;
293 | margin-right: 10px;
294 | cursor: pointer;
295 | user-select: none;
296 | position: relative;
297 | z-index: 99;
298 | font-weight: bold;
299 | }
300 |
301 | .copySuccess {
302 | color: green;
303 | }
304 |
305 | .disableSelect {
306 | user-select: none;
307 | }
308 |
309 | .noPassword {
310 | width: 100%;
311 | text-align: center;
312 | position: relative;
313 | }
314 |
315 | .register {
316 | font-size: 14px;
317 | }
318 |
319 | .register span {
320 | font-weight: bold;
321 | text-decoration: underline;
322 | cursor: pointer;
323 | user-select: none;
324 | }
325 |
326 | .registerSuccessTips {
327 | width: 100%;
328 | text-align: left;
329 | font-size: 12px;
330 | font-weight: bold;
331 | color: green;
332 | margin: 0px 0px 20px 0px;
333 | }
334 |
335 | .registerSuccessTips .code {
336 | font-size: 16px;
337 | }
338 |
339 | /* Enable hover only on non-touch devices */
340 | @media (hover: hover) and (pointer: fine) {
341 | .card:hover {
342 | background: rgba(var(--card-rgb), 0.1);
343 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
344 | }
345 |
346 | .card:hover span {
347 | transform: translateX(4px);
348 | }
349 | }
350 |
351 | @media (prefers-reduced-motion) {
352 | .card:hover span {
353 | transform: none;
354 | }
355 | }
356 |
357 | @media (max-width: 600px) {
358 |
359 | .main {
360 | padding: 0;
361 | }
362 |
363 | .loginForm {
364 | width: 300px;
365 | }
366 |
367 | .password {
368 | min-width: 330px;
369 | padding: 10px;
370 | margin: 0 auto;
371 | margin-bottom: 20px;
372 | }
373 | }
374 |
375 | /* Mobile */
376 | @media (max-width: 700px) {
377 | .content {
378 | padding: 4rem;
379 | }
380 |
381 | .grid {
382 | grid-template-columns: 1fr;
383 | margin-bottom: 120px;
384 | max-width: 320px;
385 | text-align: center;
386 | }
387 |
388 | .card {
389 | padding: 1rem 2.5rem;
390 | }
391 |
392 | .card h2 {
393 | margin-bottom: 0.5rem;
394 | }
395 |
396 | .center {
397 | padding: 8rem 0 6rem;
398 | position: relative;
399 | }
400 |
401 | .center::before {
402 | transform: none;
403 | height: 300px;
404 | }
405 |
406 | .description {
407 | font-size: 0.8rem;
408 | }
409 |
410 | .description a {
411 | padding: 1rem;
412 | }
413 |
414 | .description p,
415 | .description div {
416 | display: flex;
417 | justify-content: center;
418 | position: fixed;
419 | width: 100%;
420 | }
421 |
422 | .description p {
423 | align-items: center;
424 | inset: 0 0 auto;
425 | padding: 2rem 1rem 1.4rem;
426 | border-radius: 0;
427 | border: none;
428 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
429 | background: linear-gradient(to bottom,
430 | rgba(var(--background-start-rgb), 1),
431 | rgba(var(--callout-rgb), 0.5));
432 | background-clip: padding-box;
433 | backdrop-filter: blur(24px);
434 | }
435 |
436 | .description div {
437 | align-items: flex-end;
438 | inset: auto 0 0;
439 | padding: 2rem;
440 | height: 100px;
441 | background: linear-gradient(to bottom,
442 | transparent 0%,
443 | rgb(var(--background-end-rgb)) 40%);
444 | z-index: 1;
445 | }
446 | }
447 |
448 | /* Tablet and Smaller Desktop */
449 | @media (min-width: 701px) and (max-width: 1120px) {
450 | .grid {
451 | grid-template-columns: repeat(2, 50%);
452 | }
453 |
454 | .password {
455 | width: 100%;
456 | }
457 | }
458 |
459 | @media (prefers-color-scheme: dark) {
460 | .vercelLogo {
461 | filter: invert(1);
462 | }
463 |
464 | .logo {
465 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
466 | }
467 | }
468 |
469 | @keyframes rotate {
470 | from {
471 | transform: rotate(360deg);
472 | }
473 |
474 | to {
475 | transform: rotate(0deg);
476 | }
477 | }
--------------------------------------------------------------------------------
/web/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import styles from "./page.module.css";
3 | import { useEffect, useState, useRef } from "react";
4 | import Image from "next/image";
5 | import { login } from "@/apis/login";
6 | import { getPassword } from "@/apis/getPassword";
7 | import { get2faToken } from "@/apis/get2faToken";
8 | import { deletePassword } from "@/apis/deletePassword";
9 | import { addPassword } from "@/apis/addPassword";
10 | import { editNewPassword } from "@/apis/editNewPassword";
11 | import { register } from "@/apis/register";
12 | import Loading from "@/components/loading/page";
13 |
14 | export default function Home() {
15 |
16 | const [token, setToken] = useState("");
17 |
18 | const [localToken, setLocalToken] = useState("");
19 |
20 | const [loadingStatus, setLoadingStatus] = useState(true);
21 |
22 | const [passwordList, setPasswordList] = useState([]);
23 |
24 | const [copiedIndex, setCopiedIndex] = useState(null);
25 |
26 | const [deleteStaus, setDeleteStaus] = useState(false);
27 |
28 | const [newPasswordStatus, setNewPasswordStatus] = useState(false);
29 |
30 | const [editPasswordStatus, setEditPasswordStatus] = useState(false);
31 |
32 | const [editAddPasswordStatus, setEditAddPasswordStatus] = useState(false);
33 |
34 | const [addPasswordStatus, setAddPasswordStatus] = useState(false);
35 |
36 | const [settingIndex, setSettingIndex] = useState(null);
37 |
38 | const [loginTips, setLoginTips] = useState("");
39 |
40 | const [loginStatus, setLoginStatus] = useState(false);
41 |
42 | const [registerTips, setRegisterTips] = useState("");
43 |
44 | const [registerToken, setRegisterToken] = useState("");
45 |
46 | const [registerStatus, setRegisterStatus] = useState(false);
47 |
48 | const [registerRequestStatus, setRegisterRequestStatus] = useState(false);
49 |
50 | const [registerSuccessStatus, setRegisterSuccessStatus] = useState(false);
51 |
52 | const [registerUserName, setRegisterUserName] = useState("");
53 |
54 | const [twofaIndex, setTwofaIndex] = useState(null);
55 |
56 | const [twofa, setTwofa] = useState(null);
57 |
58 | const copiedTimeoutRef = useRef(null);
59 |
60 | const twofaTimeoutRef = useRef(null);
61 |
62 | const newPasswordData = {
63 | appName: "",
64 | webSite: "",
65 | account: "",
66 | password: "",
67 | twofa: "",
68 | }
69 |
70 | const [newPassword, setNewPassword] = useState(newPasswordData);
71 |
72 | const [editPassword, setEditPassword] = useState(newPasswordData);
73 |
74 | useEffect(() => {
75 | const localToken = localStorage.getItem("localToken");
76 | if (localToken) {
77 | loginRequest(localToken)
78 | return
79 | }
80 |
81 | setLoadingStatus(false)
82 | }, [])
83 |
84 | const loginUser = () => {
85 |
86 | if (token) {
87 | setLoginStatus(true)
88 | loginRequest(token)
89 | }
90 |
91 | }
92 |
93 | const registerUser = () => {
94 | if (registerUserName) {
95 |
96 | setRegisterRequestStatus(true)
97 |
98 | register({ user: registerUserName }).then(res => {
99 | setRegisterToken(res.data.token)
100 | setRegisterSuccessStatus(true)
101 | setRegisterRequestStatus(false)
102 | }).catch(err => {
103 | setRegisterRequestStatus(false)
104 | setRegisterTips(err.response.data.msg)
105 | })
106 |
107 | }
108 | }
109 |
110 | const loginRequest = (token: string) => {
111 | const requestData = {
112 | token: token,
113 | }
114 |
115 | login(requestData).then((res) => {
116 | const { token } = res.data;
117 | setLocalToken(token)
118 | localStorage.setItem("localToken", token);
119 | setLoginStatus(false)
120 | getPasswordRequest()
121 | }).catch(err => {
122 | localStorage.removeItem("localToken");
123 | setLoginTips(err.response.data.msg)
124 | setLoadingStatus(false)
125 | setLoginStatus(false)
126 | })
127 |
128 | }
129 |
130 | const getPasswordRequest = () => {
131 | getPassword().then((res) => {
132 | setPasswordList(res.data);
133 | setLoadingStatus(false)
134 | setDeleteStaus(false)
135 | setAddPasswordStatus(false)
136 | setNewPasswordStatus(false)
137 | setEditPasswordStatus(false)
138 | setEditAddPasswordStatus(false)
139 | setSettingIndex(null)
140 | }).catch(() => {
141 | console.log("error");
142 | })
143 | }
144 |
145 | const copyPassword = (password: string, index: any) => {
146 |
147 | if (copiedTimeoutRef.current) {
148 | clearTimeout(copiedTimeoutRef.current);
149 | }
150 |
151 | navigator.clipboard.writeText(password).then(() => {
152 | setCopiedIndex(index);
153 | copiedTimeoutRef.current = setTimeout(() => setCopiedIndex(null), 2000);
154 | });
155 | }
156 |
157 | const show2fa = (twofa: string, index: any) => {
158 |
159 | if (twofaTimeoutRef.current) {
160 | clearTimeout(twofaTimeoutRef.current);
161 | }
162 |
163 | get2faToken({ secret: twofa }).then(res => {
164 | setTwofaIndex(index)
165 | const dom = {res.data.token} {res.data.remainingTime}s
166 | setTwofa(dom)
167 | twofaTimeoutRef.current = setTimeout(() => setTwofaIndex(null), 3000);
168 | })
169 | }
170 |
171 | const deletePasswordRequest = (id: string) => {
172 | setDeleteStaus(true)
173 | deletePassword({ id }).then(res => {
174 | getPasswordRequest()
175 | })
176 | }
177 |
178 | const addNewPassword = () => {
179 |
180 | const data = newPassword as any;
181 |
182 | if (!data.appName) {
183 | return "appName cannot be empty";
184 | }
185 | if (!data.webSite) {
186 | return "webSite cannot be empty";
187 | }
188 | if (!data.account) {
189 | return "account cannot be empty";
190 | }
191 | if (!data.password) {
192 | return "password cannot be empty";
193 | }
194 |
195 | setAddPasswordStatus(true)
196 |
197 | addPassword(data).then(res => {
198 | getPasswordRequest();
199 | })
200 |
201 | }
202 |
203 | const editNewPasswordRequest = () => {
204 | const data = editPassword as any;
205 |
206 | if (!data.appName) {
207 | return "appName cannot be empty";
208 | }
209 | if (!data.webSite) {
210 | return "webSite cannot be empty";
211 | }
212 | if (!data.account) {
213 | return "account cannot be empty";
214 | }
215 | if (!data.password) {
216 | return "password cannot be empty";
217 | }
218 |
219 | setEditPasswordStatus(true)
220 |
221 | setEditAddPasswordStatus(true)
222 |
223 | editNewPassword(data).then(res => {
224 | getPasswordRequest();
225 | })
226 | }
227 |
228 | return (
229 |
230 |
231 |
232 |
233 | Password Next
234 |
235 |
236 | {!localToken && (
237 |
238 | By HChaoHui
239 |
240 | )}
241 | {(localToken && !newPasswordStatus && !editPasswordStatus) && (
242 |
{ setNewPasswordStatus(true); setNewPassword(newPasswordData) }}>
243 | + New Password
244 |
245 | )}
246 | {(localToken && newPasswordStatus) && (
247 |
{ setNewPasswordStatus(false) }}>
248 | Cancel
249 |
250 | )}
251 | {(localToken && editPasswordStatus) && (
252 |
{ setEditPasswordStatus(false) }}>
253 | Cancel
254 |
255 | )}
256 |
257 |
258 |
259 |
260 | {(!localToken && !registerStatus) && (
261 |
262 |
263 |
{loginTips}
264 |
265 | {!loginStatus && (
266 |
{ loginUser() }}>Login
267 | )}
268 |
269 | {loginStatus && (
270 |
Login...
271 | )}
272 |
273 |
No account? { setRegisterStatus(true) }}>Click to register.
274 |
275 | )}
276 |
277 | {(!localToken && registerStatus) && (
278 |
279 |
280 |
{registerTips}
281 |
282 | {registerSuccessStatus && (
283 |
284 | Register Success.
285 | Token: {registerToken}
286 | Please keep the Token safe, as it will not appear again and serves as your sole login credential.
287 |
288 | )}
289 |
290 | {!registerRequestStatus && (
291 |
{ registerUser() }}>Register
292 | )}
293 |
294 | {registerRequestStatus && (
295 |
Register...
296 | )}
297 |
298 |
Already have an account? { setRegisterStatus(false) }}>Click to log in.
299 |
300 | )}
301 |
302 | {(passwordList.length > 0 && !newPasswordStatus && !editPasswordStatus) && (
303 |
304 | {passwordList.map((item, index) => {
305 | return (
306 |
307 |
308 | AppName: {item.appName}
309 |
310 |
313 |
314 | Account: {item.account}
315 |
316 |
317 | Password:
318 | { copyPassword(item.password, index) }}>******
319 | {copiedIndex === index && Copied Success!!! }
320 |
321 | {item.twofa && (
322 |
323 | 2fa:
324 | {twofaIndex == index ? twofa : { show2fa(item.twofa, index) }}>****** }
325 |
326 | )}
327 |
328 |
{ setSettingIndex(item.id) }}>
329 |
330 |
331 |
332 | {settingIndex == item.id && (
333 |
334 |
335 |
336 |
{ setEditPasswordStatus(true); setEditPassword({ ...item }) }}>Edit
337 | {!deleteStaus && (
338 |
{ deletePasswordRequest(item.id) }}>Delete
339 | )}
340 | {deleteStaus && (
341 |
Deleteing...
342 | )}
343 |
344 |
345 |
{ setSettingIndex(null) }}>
346 |
347 |
348 |
349 |
350 | )}
351 |
352 |
353 | )
354 | })}
355 |
356 | )}
357 |
358 | {(passwordList.length == 0 && !newPasswordStatus && !editPasswordStatus && localToken) && (
359 |
360 | No password set, please add one.
361 |
362 | )}
363 |
364 | {newPasswordStatus && (
365 |
366 |
367 |
368 |
Account:
369 |
{ setNewPassword({ ...newPassword, account: e.target.value }) }} />
370 |
371 |
372 |
Password:
373 |
{ setNewPassword({ ...newPassword, password: e.target.value }) }} />
374 |
375 |
376 |
AppName:
377 |
{ setNewPassword({ ...newPassword, appName: e.target.value }) }} />
378 |
379 |
380 |
WebSite:
381 |
{ setNewPassword({ ...newPassword, webSite: e.target.value }) }} />
382 |
383 |
384 |
2fa:
385 |
{ setNewPassword({ ...newPassword, twofa: e.target.value }) }} />
386 |
387 |
388 | {!addPasswordStatus && (
389 |
390 |
{ addNewPassword() }}>Submit
391 |
392 | )}
393 |
394 | {addPasswordStatus && (
395 |
398 | )}
399 |
400 |
401 | )}
402 |
403 | {editPasswordStatus && (
404 |
405 |
406 |
407 |
Account:
408 |
{ setEditPassword({ ...editPassword, account: e.target.value }) }} />
409 |
410 |
411 |
Password:
412 |
{ setEditPassword({ ...editPassword, password: e.target.value }) }} />
413 |
414 |
415 |
AppName:
416 |
{ setEditPassword({ ...editPassword, appName: e.target.value }) }} />
417 |
418 |
419 |
WebSite:
420 |
{ setEditPassword({ ...editPassword, webSite: e.target.value }) }} />
421 |
422 |
423 |
2fa:
424 |
{ setEditPassword({ ...editPassword, twofa: e.target.value }) }} />
425 |
426 |
427 | {!editAddPasswordStatus && (
428 |
429 |
{ editNewPasswordRequest() }}>Submit
430 |
431 | )}
432 |
433 | {editAddPasswordStatus && (
434 |
437 | )}
438 |
439 |
440 | )}
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 | {loadingStatus && }
449 |
450 | );
451 | }
452 |
--------------------------------------------------------------------------------
/web/src/components/loading/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | width: 100vw;
3 | height: 100vh;
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | z-index: 99;
8 | background-color:#FFFFFF;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | color: #666666;
13 | font-weight: bold;
14 | font-size: 20px;
15 | }
16 |
--------------------------------------------------------------------------------
/web/src/components/loading/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import styles from "./page.module.css";
3 | export default function Loading() {
4 |
5 | return (
6 |
7 | loading...
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/web/src/config/config.ts:
--------------------------------------------------------------------------------
1 | const config = {
2 | textInputMaxLength: 5000,
3 | }
4 |
5 | export default config
--------------------------------------------------------------------------------
/web/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const api: string = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:19899';
4 |
5 | const request = axios.create({
6 | baseURL: api,
7 | timeout: 60000,
8 | headers: {
9 | 'Content-Type': 'application/json'
10 | },
11 | });
12 |
13 | const getToken = () => {
14 | if (typeof window !== 'undefined') {
15 | return localStorage.getItem('localToken');
16 | }
17 | return null;
18 | };
19 |
20 | const Get = (url: string, params: object) => {
21 | const token = getToken();
22 | return request.get(url, {
23 | params,
24 | headers: {
25 | 'Authorization': token ? `Bearer ${token}` : '',
26 | },
27 | });
28 | };
29 |
30 | const Post = (url: string, data: object) => {
31 | const token = getToken();
32 | return request.post(url, data, {
33 | headers: {
34 | 'Authorization': token ? `Bearer ${token}` : '',
35 | },
36 | });
37 | };
38 |
39 | export { Get, Post };
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------