Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions src/database/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,6 @@ export const migrations: Migration[] = [
database.user.update("id", userRootId, "token", userRootToken);
}
}

const userTokens = database.user.getAll(["token"]);

let userTokenUnhashed = false;
for (const entry of userTokens) {
// combo separator
if (!entry.token.includes(" ")) {
userTokenUnhashed = true;
break;
}
}

if (userTokenUnhashed) {
log.warn(
"Users with plain tokens found!",
"New users in the instance will have their token hashed,",
"In the future we will enforce that every user token is hashed."
);
}
},
sql: (await import("./migrations/0002.sql", { with: { type: "text" } })).default
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export default new Hono<Env>().delete(
"/:name",
describeRoute({
tags: ["DOCUMENT (v1)"],
summary: "Delete document",
description: "Deletes a published document in the instance",
summary: "Drop document",
description: "Deletes a document in the instance",
security: [{}, { bearer: [] }],
responses: {
200: {
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/document/v1/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default new Hono<Env>().get(
describeRoute({
tags: ["DOCUMENT (v1)"],
summary: "Get document",
description: `Get the content/metadata of a published document in the instance
description: `Fetch the content/metadata of a document in the instance

Note: If you only need to query the document metadata, you should use HEAD method instead`,
responses: {
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/document/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { Hono } from "hono/tiny";

import type { Env } from "#http/handler.ts";

import delete_ from "./delete.ts";
import drop from "./drop.ts";
import get from "./get.ts";
import list from "./list.ts";
import patch from "./patch.ts";
import post from "./post.ts";

export const v1DocumentHandler = new Hono<Env>();

v1DocumentHandler.route("/", delete_);
v1DocumentHandler.route("/", drop);
v1DocumentHandler.route("/", get);
v1DocumentHandler.route("/", list);
v1DocumentHandler.route("/", patch);
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/document/v1/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default new Hono<Env>().patch(
describeRoute({
tags: ["DOCUMENT (v1)"],
summary: "Alter document",
description: `Edit the content/metadata of a published document in the instance
description: `Edit the content/metadata of a document in the instance

Note: You can't move the ownership of a document, duplicate the document instead

Expand Down
40 changes: 40 additions & 0 deletions src/endpoints/user/v1/drop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describeRoute } from "@hono/openapi";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";

export default new Hono<Env>().delete(
"/",
describeRoute({
tags: ["USER (v1)"],
summary: "Drop user",
description: `Deletes a user in the instance

Note: All documents owned by the user will also be deleted`,
security: [{ bearer: [] }],
responses: {
200: {
description: constantHttpStatusCodes[200]
},
400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] },
404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] },

// auth middleware
401: { ...genericErrorResponse, description: constantHttpStatusCodes[401] }
}
}),
authMiddleware,
(ctx) => {
const userId = ctx.get("userId");
if (!userId) {
return errorThrow(ErrorCode.UserInvalidToken);
}

mutable.database.user.delete("id", userId);

return ctx.body(null);
Comment thread
inetol marked this conversation as resolved.
}
);
4 changes: 4 additions & 0 deletions src/endpoints/user/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Hono } from "hono/tiny";
import type { Env } from "#http/handler.ts";

import create from "./create.ts";
import drop from "./drop.ts";
import rotateToken from "./rotateToken.ts";

export const v1UserHandler = new Hono<Env>();

v1UserHandler.route("/", create);
v1UserHandler.route("/", drop);
v1UserHandler.route("/", rotateToken);
58 changes: 58 additions & 0 deletions src/endpoints/user/v1/rotateToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describeRoute, resolver } from "@hono/openapi";
import { type } from "arktype";
import { Hono } from "hono/tiny";

import { constantHttpStatusCodes, mutable } from "#/global.ts";
import type { Env } from "#http/handler.ts";
import { authMiddleware } from "#http/middleware/authorization.ts";
import { generateHash } from "#util/crypto.ts";
import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts";
import { generateToken } from "#util/user.ts";
import { validatorUserToken } from "#util/validator/user.ts";

const schemaBodyResponse = resolver(
type({
token: validatorUserToken
})
);

export default new Hono<Env>().post(
"/token",
describeRoute({
tags: ["USER (v1)"],
summary: "Rotate user token",
description: "Rotates a user token in the instance",
security: [{ bearer: [] }],
responses: {
200: {
content: {
"application/json": {
schema: schemaBodyResponse
}
},
description: constantHttpStatusCodes[200]
},
400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] },
404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] },

// auth middleware
401: { ...genericErrorResponse, description: constantHttpStatusCodes[401] }
}
}),
authMiddleware,
(ctx) => {
const userId = ctx.get("userId");
if (!userId) {
return errorThrow(ErrorCode.UserInvalidToken);
}

const token = generateToken(userId);
const hash = generateHash(token);

mutable.database.user.update("id", userId, "token", hash.combo);

return ctx.json({
token: token
});
}
);
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { abortable } from "@std/async";

import { constantStoreDispose } from "#/global.ts";
import { initDatabase, initDirStruct, initHTTPServer, initTask } from "#/init.ts";
import { initDatabase, initDirStruct, initHTTPServer, initUnhashedTokenCheck, initTask } from "#/init.ts";
import { handler } from "#http/handler.ts";
import { Logger } from "#util/console.ts";

Expand Down Expand Up @@ -59,6 +59,7 @@ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP", "SIGUSR1", "SIGUSR2"] satis
try {
await Promise.all([initDirStruct(), initHTTPServer()]);
await initDatabase();
initUnhashedTokenCheck();
initTask();
await initHTTPServer(handler().fetch);
} catch (error) {
Expand Down
24 changes: 24 additions & 0 deletions src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { Database } from "#db/index.ts";
import { http } from "#http/index.ts";
import { taskRegister } from "#task/index.ts";
import { sweeper } from "#task/list/sweeper.ts";
import { Logger } from "#util/console.ts";
import { env } from "#util/env.ts";

import { constantPathStructStorage, constantPathStructStorageData, constantStoreDispose, mutable } from "./global.ts";

const log: Logger = new Logger();

export const initDirStruct = async (): Promise<void> => {
await Promise.all([ensureDir(constantPathStructStorage), ensureDir(constantPathStructStorageData)]);
};
Expand Down Expand Up @@ -54,3 +57,24 @@ export const initTask = (): void => {
name: "sweeper"
});
};

export const initUnhashedTokenCheck = (): void => {
const userTokens = mutable.database.user.getAll(["token"]);

let userUnhashedToken = false;
for (const entry of userTokens) {
// combo separator
if (!entry.token.includes(" ")) {
userUnhashedToken = true;
break;
}
}

Comment thread
inetol marked this conversation as resolved.
if (userUnhashedToken) {
log.warn(
"Users with unhashed tokens found!",
"Those users may lose access in future versions of JSPaste!",
"See: https://github.com/jspaste/backend/issues/318"
);
}
};