Compare commits
3 Commits
4a775fc8fb
...
69d31c1179
| Author | SHA1 | Date |
|---|---|---|
|
|
69d31c1179 | |
|
|
c4e3d4cb9b | |
|
|
bf936548d5 |
|
|
@ -84,3 +84,5 @@ ERR_code_invalid: Incorrect code
|
|||
ERR_code_not_found: Code not found
|
||||
ERR_user_not_found: User not found
|
||||
ERR_password_invalid: Incorrect password
|
||||
ERR_not_mailverify: Email not verified
|
||||
ERR_auth_failed: Auth failed
|
||||
|
|
|
|||
|
|
@ -84,3 +84,5 @@ ERR_code_invalid: コードが正しくありません
|
|||
ERR_code_not_found: コードが見つかりません
|
||||
ERR_user_not_found: ユーザーが見つかりません
|
||||
ERR_password_invalid: パスワードが異なります
|
||||
ERR_not_mailverify: メール認証されていません
|
||||
ERR_auth_failed: 認証に失敗しました
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Peas is a SNS designed for sharing profiles.",
|
||||
"scripts": {
|
||||
"build:locale": "tsc"
|
||||
},
|
||||
"keywords": [
|
||||
"sns",
|
||||
"profile"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/nodemailer": "^7.0.2",
|
||||
"bcrypt": "^6.0.0",
|
||||
"child_process": "^1.0.2",
|
||||
"daisyui": "^5.1.25",
|
||||
"fs": "0.0.1-security",
|
||||
"globals": "^16.4.0",
|
||||
|
|
@ -26,7 +27,6 @@
|
|||
"nodemailer": "^7.0.6",
|
||||
"path": "^0.12.7",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"ua-parser-js": "^2.0.5",
|
||||
"url": "^0.11.4",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ importers:
|
|||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
child_process:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
daisyui:
|
||||
specifier: ^5.1.25
|
||||
version: 5.1.26
|
||||
|
|
@ -50,9 +53,6 @@ importers:
|
|||
tailwindcss:
|
||||
specifier: ^4.1.13
|
||||
version: 4.1.14
|
||||
ua-parser-js:
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5
|
||||
url:
|
||||
specifier: ^0.11.4
|
||||
version: 0.11.4
|
||||
|
|
@ -754,6 +754,9 @@ packages:
|
|||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
child_process@1.0.2:
|
||||
resolution: {integrity: sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==}
|
||||
|
||||
chownr@3.0.0:
|
||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -765,9 +768,6 @@ packages:
|
|||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
detect-europe-js@0.1.2:
|
||||
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
|
||||
|
||||
detect-libc@1.0.3:
|
||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
|
@ -880,9 +880,6 @@ packages:
|
|||
is-property@1.0.2:
|
||||
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
|
||||
|
||||
is-standalone-pwa@0.1.1:
|
||||
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
|
@ -1097,20 +1094,9 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ua-is-frozen@0.1.2:
|
||||
resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
|
||||
|
||||
ua-parser-js@2.0.5:
|
||||
resolution: {integrity: sha512-sZErtx3rhpvZQanWW5umau4o/snfoLqRcQwQIZ54377WtRzIecnIKvjpkd5JwPcSUMglGnbIgcsQBGAbdi3S9Q==}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@7.16.0:
|
||||
resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
url@0.11.4:
|
||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -2064,14 +2050,14 @@ snapshots:
|
|||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
child_process@1.0.2: {}
|
||||
|
||||
chownr@3.0.0: {}
|
||||
|
||||
daisyui@5.1.26: {}
|
||||
|
||||
denque@2.1.0: {}
|
||||
|
||||
detect-europe-js@0.1.2: {}
|
||||
|
||||
detect-libc@1.0.3: {}
|
||||
|
||||
detect-libc@2.1.1: {}
|
||||
|
|
@ -2195,8 +2181,6 @@ snapshots:
|
|||
|
||||
is-property@1.0.2: {}
|
||||
|
||||
is-standalone-pwa@0.1.1: {}
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
lightningcss-darwin-arm64@1.30.1:
|
||||
|
|
@ -2379,19 +2363,8 @@ snapshots:
|
|||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ua-is-frozen@0.1.2: {}
|
||||
|
||||
ua-parser-js@2.0.5:
|
||||
dependencies:
|
||||
detect-europe-js: 0.1.2
|
||||
is-standalone-pwa: 0.1.1
|
||||
ua-is-frozen: 0.1.2
|
||||
undici: 7.16.0
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@7.16.0: {}
|
||||
|
||||
url@0.11.4:
|
||||
dependencies:
|
||||
punycode: 1.4.1
|
||||
|
|
|
|||
|
|
@ -24,7 +24,40 @@ export async function getScopeAPIToken(token: string) {
|
|||
[token]
|
||||
);
|
||||
|
||||
if (tokenData === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const scope: scope[] = tokenData[0].scope.split(",");
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
export async function Auth(token: string, scopes: scope[]) {
|
||||
const scope = await getScopeAPIToken(token);
|
||||
if (scope === false)
|
||||
return false;
|
||||
if (scope.indexOf("admin:client") !== -1)
|
||||
return true;
|
||||
if (
|
||||
scope.indexOf("user:client") !== -1 &&
|
||||
!scopes.some(s => s.includes("admin"))
|
||||
)
|
||||
return true;
|
||||
if (scopes.sort().toString() === scope.sort().toString())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function isSignin(token: string) {
|
||||
return await getScopeAPIToken(token) !== false;
|
||||
}
|
||||
|
||||
export function getHeaderToken(header: string) {
|
||||
const match = header.match(/^Bearer\s(.+)$/);
|
||||
|
||||
if (match && match.length > 1) {
|
||||
return match[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ API.route("/signin", SignInAPI);
|
|||
import mailverifyAPI from "./mailverify.js";
|
||||
API.route("/mailverify", mailverifyAPI);
|
||||
|
||||
import TimeLine from "./timeline/main.js";
|
||||
API.route("/timelime", TimeLine);
|
||||
|
||||
import NotFound from "./notfound.js";
|
||||
API.route("*", NotFound);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ import { getKeys as TurnstileKeys, isEnabled as Turnstile } from "../../lib/turn
|
|||
import { getConnInfo } from "@hono/node-server/conninfo";
|
||||
import { compareSync as passwordHashCheck } from "bcrypt";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import MailVerifySend from "../../lib/mailverify.js";
|
||||
import { createAPIToken } from "src/lib/token.js";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { setCookie } from "hono/cookie";
|
||||
import type scope from "../../../types/api/scope.js";
|
||||
|
||||
const SignInAPI = new Hono();
|
||||
|
||||
|
|
@ -26,7 +25,7 @@ SignInAPI.post("/", async (c) => {
|
|||
|
||||
if (
|
||||
body.username.length < 3 ||
|
||||
body.username.length > 15 ||
|
||||
body.username.length > 20 ||
|
||||
body.password.length < 8 ||
|
||||
body.password.length > 15
|
||||
) {
|
||||
|
|
@ -95,10 +94,22 @@ SignInAPI.post("/", async (c) => {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
if (user[0].mailverify === 0) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: "not_mailverify",
|
||||
}, 400);
|
||||
}
|
||||
|
||||
if (passwordHashCheck(body.password, user[0].password)) {
|
||||
const scope: scope[] = ["user:client"];
|
||||
if (user[0].isAdmin === 1) {
|
||||
scope.push("admin:client");
|
||||
}
|
||||
|
||||
const token = await createAPIToken(
|
||||
body.username,
|
||||
["client"],
|
||||
scope,
|
||||
"Peas Client"
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ SignUpAPI.post("/", async (c) => {
|
|||
|
||||
if (
|
||||
body.username.length < 3 ||
|
||||
body.username.length > 15 ||
|
||||
body.username.length > 20 ||
|
||||
body.password.length < 8 ||
|
||||
body.password.length > 15
|
||||
) {
|
||||
|
|
@ -107,9 +107,18 @@ SignUpAPI.post("/", async (c) => {
|
|||
|
||||
const passwordHash = await generatePasswordHash(body.password, 10);
|
||||
|
||||
const [otherUser] = await pool.execute<RowDataPacket[]>(
|
||||
"SELECT * FROM users",
|
||||
);
|
||||
|
||||
let isAdmin = 0;
|
||||
if (otherUser.length === 0) {
|
||||
isAdmin = 1;
|
||||
}
|
||||
|
||||
const [result] = await pool.execute<RowDataPacket[]>(
|
||||
"INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`) VALUES (?, ?, ?, '0', current_timestamp(3))",
|
||||
[body.username, passwordHash, body.email],
|
||||
"INSERT INTO `users` (`id`, `name`, `password`, `email`, `mailverified`, `isAdmin`, `time`) VALUES (?, ?, ?, ?, '0', ?, current_timestamp(3))",
|
||||
[body.username, body.username, passwordHash, body.email, String(isAdmin)],
|
||||
);
|
||||
|
||||
if ((result as RowDataPacket).affectedRows === 1) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import { Hono } from "hono";
|
||||
|
||||
const Community = new Hono();
|
||||
|
||||
export default Community;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Hono } from "hono";
|
||||
|
||||
const TimeLine = new Hono();
|
||||
|
||||
import Users from "./users.js";
|
||||
TimeLine.route("/users", Users);
|
||||
|
||||
import Community from "./community.js";
|
||||
TimeLine.route("/community", Community);
|
||||
|
||||
export default TimeLine;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Hono } from "hono";
|
||||
import { Auth, getHeaderToken } from "../../../lib/token.js";
|
||||
import pool from "../../../lib/database.js";
|
||||
import type { RowDataPacket } from "mysql2";
|
||||
|
||||
const Users = new Hono();
|
||||
|
||||
Users.post("/", async (c) => {
|
||||
const body = await c.req.json();
|
||||
if (!Auth(getHeaderToken(c.req.header("Authorization") ?? ""), ["read:user"])) {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: "auth_failed",
|
||||
});
|
||||
}
|
||||
|
||||
let page = 1;
|
||||
if (
|
||||
typeof body.page === "string" &&
|
||||
!isNaN(Number(body.page)) &&
|
||||
Number(body.page) >= 1
|
||||
) {
|
||||
page = Number(body.page);
|
||||
}
|
||||
|
||||
const [usersData] = await pool.execute<RowDataPacket[]>(
|
||||
"SELECT * FROM users"
|
||||
);
|
||||
|
||||
let result = [];
|
||||
for (let i = usersData.length - 1; i < 0; i--) {
|
||||
result.push({
|
||||
username: usersData[i].id,
|
||||
name: usersData[i].name,
|
||||
isAdmin: usersData[i].isAdmin === 1 ? true: false,
|
||||
createAt: usersData[i].time,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
users: result,
|
||||
});
|
||||
});
|
||||
|
||||
export default Users;
|
||||
|
|
@ -4,11 +4,14 @@ import type InfoAPI from "../../../types/api/info";
|
|||
import Config from "../../../config/peas.config.js";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { Locale } from "../../lib/locale.js";
|
||||
import { isSignin } from "../../lib/token.js";
|
||||
|
||||
const Home = new Hono();
|
||||
|
||||
Home.get("/", async (c) => {
|
||||
if (getCookie(c, "token") === undefined) return c.redirect("/signin");
|
||||
if (await isSignin(getCookie(c, "token") ?? "") === false) {
|
||||
return c.redirect("/signin");
|
||||
}
|
||||
|
||||
const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
|
||||
method: "POST",
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ import Icon from "../../components/iconify.js";
|
|||
import { Locale } from "../../lib/locale.js";
|
||||
import toComponent from "../../lib/toComponent.js";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { getScopeAPIToken } from "../../lib/token.js";
|
||||
import { isSignin } from "../../lib/token.js";
|
||||
|
||||
const Index = new Hono();
|
||||
|
||||
Index.get("/", async (c) => {
|
||||
if (getCookie(c, "token") !== undefined) {
|
||||
const scope = await getScopeAPIToken(getCookie(c, "token") ?? "");
|
||||
if (scope !== undefined) return c.redirect("/home");
|
||||
if (await isSignin(getCookie(c, "token") ?? "")) {
|
||||
return c.redirect("/home");
|
||||
}
|
||||
|
||||
const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@ import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "..
|
|||
import Turnstile from "../../components/turnstile.js";
|
||||
import { html } from "hono/html";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { getScopeAPIToken } from "../../lib/token.js";
|
||||
import { isSignin } from "../../lib/token.js";
|
||||
|
||||
const MailVerify = new Hono();
|
||||
|
||||
MailVerify.get("/", async (c) => {
|
||||
if (getCookie(c, "token") !== undefined) {
|
||||
const scope = await getScopeAPIToken(getCookie(c, "token") ?? "");
|
||||
if (scope !== undefined) return c.redirect("/home");
|
||||
if (await isSignin(getCookie(c, "token") ?? "")) {
|
||||
return c.redirect("/home");
|
||||
}
|
||||
|
||||
const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
|
||||
|
|
|
|||
|
|
@ -9,14 +9,13 @@ import Turnstile from "../../components/turnstile.js";
|
|||
import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js";
|
||||
import { html } from "hono/html";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { getScopeAPIToken } from "../../lib/token.js";
|
||||
import { isSignin } from "../../lib/token.js";
|
||||
|
||||
const SignIn = new Hono();
|
||||
|
||||
SignIn.get("/", async (c) => {
|
||||
if (getCookie(c, "token") !== undefined) {
|
||||
const scope = await getScopeAPIToken(getCookie(c, "token") ?? "");
|
||||
if (scope !== undefined) return c.redirect("/home");
|
||||
if (await isSignin(getCookie(c, "token") ?? "")) {
|
||||
return c.redirect("/home");
|
||||
}
|
||||
|
||||
const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@ import Turnstile from "../../components/turnstile.js";
|
|||
import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js";
|
||||
import { html } from "hono/html";
|
||||
import { getCookie } from "hono/cookie";
|
||||
import { getScopeAPIToken } from "../../lib/token.js";
|
||||
import { isSignin } from "../../lib/token.js";
|
||||
|
||||
const SignUp = new Hono();
|
||||
|
||||
SignUp.get("/", async (c) => {
|
||||
if (getCookie(c, "token") !== undefined) {
|
||||
const scope = await getScopeAPIToken(getCookie(c, "token") ?? "");
|
||||
if (scope !== undefined) return c.redirect("/home");
|
||||
if (await isSignin(getCookie(c, "token") ?? "")) {
|
||||
return c.redirect("/home");
|
||||
}
|
||||
|
||||
const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
type scope =
|
||||
"read:user" |
|
||||
"client";
|
||||
"admin:client" |
|
||||
"user:client";
|
||||
|
||||
export default scope;
|
||||
|
|
|
|||
|
|
@ -88,10 +88,12 @@ DROP TABLE IF EXISTS `users`;
|
|||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `users` (
|
||||
`id` text NOT NULL,
|
||||
`id` VARCHAR(20) NOT NULL,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`password` text NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`mailverified` int(1) NOT NULL DEFAULT 0,
|
||||
`isAdmin` int(1) NOT NULL DEFAULT 0,
|
||||
`time` datetime(3) NOT NULL DEFAULT current_timestamp(3),
|
||||
`num` int(11) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`num`),
|
||||
|
|
|
|||
Loading…
Reference in New Issue