forked from peas-dev/peas
1
0
Fork 0

Compare commits

..

3 Commits

19 changed files with 159 additions and 65 deletions

View File

@ -84,3 +84,5 @@ ERR_code_invalid: Incorrect code
ERR_code_not_found: Code not found ERR_code_not_found: Code not found
ERR_user_not_found: User not found ERR_user_not_found: User not found
ERR_password_invalid: Incorrect password ERR_password_invalid: Incorrect password
ERR_not_mailverify: Email not verified
ERR_auth_failed: Auth failed

View File

@ -84,3 +84,5 @@ ERR_code_invalid: コードが正しくありません
ERR_code_not_found: コードが見つかりません ERR_code_not_found: コードが見つかりません
ERR_user_not_found: ユーザーが見つかりません ERR_user_not_found: ユーザーが見つかりません
ERR_password_invalid: パスワードが異なります ERR_password_invalid: パスワードが異なります
ERR_not_mailverify: メール認証されていません
ERR_auth_failed: 認証に失敗しました

View File

@ -3,9 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"description": "Peas is a SNS designed for sharing profiles.", "description": "Peas is a SNS designed for sharing profiles.",
"scripts": {
"build:locale": "tsc"
},
"keywords": [ "keywords": [
"sns", "sns",
"profile" "profile"

View File

@ -17,6 +17,7 @@
"@types/bcrypt": "^6.0.0", "@types/bcrypt": "^6.0.0",
"@types/nodemailer": "^7.0.2", "@types/nodemailer": "^7.0.2",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"child_process": "^1.0.2",
"daisyui": "^5.1.25", "daisyui": "^5.1.25",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"globals": "^16.4.0", "globals": "^16.4.0",
@ -26,7 +27,6 @@
"nodemailer": "^7.0.6", "nodemailer": "^7.0.6",
"path": "^0.12.7", "path": "^0.12.7",
"tailwindcss": "^4.1.13", "tailwindcss": "^4.1.13",
"ua-parser-js": "^2.0.5",
"url": "^0.11.4", "url": "^0.11.4",
"uuid": "^13.0.0" "uuid": "^13.0.0"
}, },

View File

@ -23,6 +23,9 @@ importers:
bcrypt: bcrypt:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
child_process:
specifier: ^1.0.2
version: 1.0.2
daisyui: daisyui:
specifier: ^5.1.25 specifier: ^5.1.25
version: 5.1.26 version: 5.1.26
@ -50,9 +53,6 @@ importers:
tailwindcss: tailwindcss:
specifier: ^4.1.13 specifier: ^4.1.13
version: 4.1.14 version: 4.1.14
ua-parser-js:
specifier: ^2.0.5
version: 2.0.5
url: url:
specifier: ^0.11.4 specifier: ^0.11.4
version: 0.11.4 version: 0.11.4
@ -754,6 +754,9 @@ packages:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
child_process@1.0.2:
resolution: {integrity: sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==}
chownr@3.0.0: chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -765,9 +768,6 @@ packages:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
detect-europe-js@0.1.2:
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
detect-libc@1.0.3: detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@ -880,9 +880,6 @@ packages:
is-property@1.0.2: is-property@1.0.2:
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
is-standalone-pwa@0.1.1:
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
jiti@2.6.1: jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
@ -1097,20 +1094,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true 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: undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici@7.16.0:
resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==}
engines: {node: '>=20.18.1'}
url@0.11.4: url@0.11.4:
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2064,14 +2050,14 @@ snapshots:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
child_process@1.0.2: {}
chownr@3.0.0: {} chownr@3.0.0: {}
daisyui@5.1.26: {} daisyui@5.1.26: {}
denque@2.1.0: {} denque@2.1.0: {}
detect-europe-js@0.1.2: {}
detect-libc@1.0.3: {} detect-libc@1.0.3: {}
detect-libc@2.1.1: {} detect-libc@2.1.1: {}
@ -2195,8 +2181,6 @@ snapshots:
is-property@1.0.2: {} is-property@1.0.2: {}
is-standalone-pwa@0.1.1: {}
jiti@2.6.1: {} jiti@2.6.1: {}
lightningcss-darwin-arm64@1.30.1: lightningcss-darwin-arm64@1.30.1:
@ -2379,19 +2363,8 @@ snapshots:
typescript@5.9.3: {} 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-types@6.21.0: {}
undici@7.16.0: {}
url@0.11.4: url@0.11.4:
dependencies: dependencies:
punycode: 1.4.1 punycode: 1.4.1

View File

@ -24,7 +24,40 @@ export async function getScopeAPIToken(token: string) {
[token] [token]
); );
if (tokenData === undefined) {
return false;
}
const scope: scope[] = tokenData[0].scope.split(","); const scope: scope[] = tokenData[0].scope.split(",");
return scope; 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 "";
}

View File

@ -14,6 +14,9 @@ API.route("/signin", SignInAPI);
import mailverifyAPI from "./mailverify.js"; import mailverifyAPI from "./mailverify.js";
API.route("/mailverify", mailverifyAPI); API.route("/mailverify", mailverifyAPI);
import TimeLine from "./timeline/main.js";
API.route("/timelime", TimeLine);
import NotFound from "./notfound.js"; import NotFound from "./notfound.js";
API.route("*", NotFound); API.route("*", NotFound);

View File

@ -5,10 +5,9 @@ import { getKeys as TurnstileKeys, isEnabled as Turnstile } from "../../lib/turn
import { getConnInfo } from "@hono/node-server/conninfo"; import { getConnInfo } from "@hono/node-server/conninfo";
import { compareSync as passwordHashCheck } from "bcrypt"; import { compareSync as passwordHashCheck } from "bcrypt";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import MailVerifySend from "../../lib/mailverify.js";
import { createAPIToken } from "src/lib/token.js"; import { createAPIToken } from "src/lib/token.js";
import { UAParser } from "ua-parser-js";
import { setCookie } from "hono/cookie"; import { setCookie } from "hono/cookie";
import type scope from "../../../types/api/scope.js";
const SignInAPI = new Hono(); const SignInAPI = new Hono();
@ -26,7 +25,7 @@ SignInAPI.post("/", async (c) => {
if ( if (
body.username.length < 3 || body.username.length < 3 ||
body.username.length > 15 || body.username.length > 20 ||
body.password.length < 8 || body.password.length < 8 ||
body.password.length > 15 body.password.length > 15
) { ) {
@ -95,10 +94,22 @@ SignInAPI.post("/", async (c) => {
}, 400); }, 400);
} }
if (user[0].mailverify === 0) {
return c.json({
success: false,
error: "not_mailverify",
}, 400);
}
if (passwordHashCheck(body.password, user[0].password)) { 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( const token = await createAPIToken(
body.username, body.username,
["client"], scope,
"Peas Client" "Peas Client"
); );

View File

@ -24,7 +24,7 @@ SignUpAPI.post("/", async (c) => {
if ( if (
body.username.length < 3 || body.username.length < 3 ||
body.username.length > 15 || body.username.length > 20 ||
body.password.length < 8 || body.password.length < 8 ||
body.password.length > 15 body.password.length > 15
) { ) {
@ -107,9 +107,18 @@ SignUpAPI.post("/", async (c) => {
const passwordHash = await generatePasswordHash(body.password, 10); 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[]>( const [result] = await pool.execute<RowDataPacket[]>(
"INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`) VALUES (?, ?, ?, '0', current_timestamp(3))", "INSERT INTO `users` (`id`, `name`, `password`, `email`, `mailverified`, `isAdmin`, `time`) VALUES (?, ?, ?, ?, '0', ?, current_timestamp(3))",
[body.username, passwordHash, body.email], [body.username, body.username, passwordHash, body.email, String(isAdmin)],
); );
if ((result as RowDataPacket).affectedRows === 1) { if ((result as RowDataPacket).affectedRows === 1) {

View File

@ -0,0 +1,5 @@
import { Hono } from "hono";
const Community = new Hono();
export default Community;

View File

@ -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;

View File

@ -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;

View File

@ -4,11 +4,14 @@ import type InfoAPI from "../../../types/api/info";
import Config from "../../../config/peas.config.js"; import Config from "../../../config/peas.config.js";
import { getCookie } from "hono/cookie"; import { getCookie } from "hono/cookie";
import { Locale } from "../../lib/locale.js"; import { Locale } from "../../lib/locale.js";
import { isSignin } from "../../lib/token.js";
const Home = new Hono(); const Home = new Hono();
Home.get("/", async (c) => { 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`, { const InfoReq = await fetch(`${Config.server.origin}/api/info`, {
method: "POST", method: "POST",

View File

@ -6,14 +6,13 @@ import Icon from "../../components/iconify.js";
import { Locale } from "../../lib/locale.js"; import { Locale } from "../../lib/locale.js";
import toComponent from "../../lib/toComponent.js"; import toComponent from "../../lib/toComponent.js";
import { getCookie } from "hono/cookie"; import { getCookie } from "hono/cookie";
import { getScopeAPIToken } from "../../lib/token.js"; import { isSignin } from "../../lib/token.js";
const Index = new Hono(); const Index = new Hono();
Index.get("/", async (c) => { Index.get("/", async (c) => {
if (getCookie(c, "token") !== undefined) { if (await isSignin(getCookie(c, "token") ?? "")) {
const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); return c.redirect("/home");
if (scope !== undefined) return c.redirect("/home");
} }
const InfoReq = await fetch(`${Config.server.origin}/api/info`, { const InfoReq = await fetch(`${Config.server.origin}/api/info`, {

View File

@ -8,14 +8,13 @@ import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "..
import Turnstile from "../../components/turnstile.js"; import Turnstile from "../../components/turnstile.js";
import { html } from "hono/html"; import { html } from "hono/html";
import { getCookie } from "hono/cookie"; import { getCookie } from "hono/cookie";
import { getScopeAPIToken } from "../../lib/token.js"; import { isSignin } from "../../lib/token.js";
const MailVerify = new Hono(); const MailVerify = new Hono();
MailVerify.get("/", async (c) => { MailVerify.get("/", async (c) => {
if (getCookie(c, "token") !== undefined) { if (await isSignin(getCookie(c, "token") ?? "")) {
const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); return c.redirect("/home");
if (scope !== undefined) return c.redirect("/home");
} }
const InfoReq = await fetch(`${Config.server.origin}/api/info`, { const InfoReq = await fetch(`${Config.server.origin}/api/info`, {

View File

@ -9,14 +9,13 @@ import Turnstile from "../../components/turnstile.js";
import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js"; import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js";
import { html } from "hono/html"; import { html } from "hono/html";
import { getCookie } from "hono/cookie"; import { getCookie } from "hono/cookie";
import { getScopeAPIToken } from "../../lib/token.js"; import { isSignin } from "../../lib/token.js";
const SignIn = new Hono(); const SignIn = new Hono();
SignIn.get("/", async (c) => { SignIn.get("/", async (c) => {
if (getCookie(c, "token") !== undefined) { if (await isSignin(getCookie(c, "token") ?? "")) {
const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); return c.redirect("/home");
if (scope !== undefined) return c.redirect("/home");
} }
const InfoReq = await fetch(`${Config.server.origin}/api/info`, { const InfoReq = await fetch(`${Config.server.origin}/api/info`, {

View File

@ -10,14 +10,13 @@ import Turnstile from "../../components/turnstile.js";
import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js"; import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js";
import { html } from "hono/html"; import { html } from "hono/html";
import { getCookie } from "hono/cookie"; import { getCookie } from "hono/cookie";
import { getScopeAPIToken } from "../../lib/token.js"; import { isSignin } from "../../lib/token.js";
const SignUp = new Hono(); const SignUp = new Hono();
SignUp.get("/", async (c) => { SignUp.get("/", async (c) => {
if (getCookie(c, "token") !== undefined) { if (await isSignin(getCookie(c, "token") ?? "")) {
const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); return c.redirect("/home");
if (scope !== undefined) return c.redirect("/home");
} }
const InfoReq = await fetch(`${Config.server.origin}/api/info`, { const InfoReq = await fetch(`${Config.server.origin}/api/info`, {

View File

@ -1,5 +1,6 @@
type scope = type scope =
"read:user" | "read:user" |
"client"; "admin:client" |
"user:client";
export default scope; export default scope;

View File

@ -88,10 +88,12 @@ DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */; /*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` ( CREATE TABLE `users` (
`id` text NOT NULL, `id` VARCHAR(20) NOT NULL,
`name` VARCHAR(50) NOT NULL,
`password` text NOT NULL, `password` text NOT NULL,
`email` text NOT NULL, `email` text NOT NULL,
`mailverified` int(1) NOT NULL DEFAULT 0, `mailverified` int(1) NOT NULL DEFAULT 0,
`isAdmin` int(1) NOT NULL DEFAULT 0,
`time` datetime(3) NOT NULL DEFAULT current_timestamp(3), `time` datetime(3) NOT NULL DEFAULT current_timestamp(3),
`num` int(11) NOT NULL AUTO_INCREMENT, `num` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`num`), PRIMARY KEY (`num`),