chomot 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/factories/app.factory.d.ts +10 -0
- package/dist/factories/app.factory.js +6 -0
- package/dist/factories/auth.factory.d.ts +6 -0
- package/dist/factories/auth.factory.js +25 -0
- package/dist/factories/index.d.ts +4 -0
- package/dist/factories/index.js +4 -0
- package/dist/factories/response.factory.d.ts +15 -0
- package/dist/factories/response.factory.js +12 -0
- package/dist/factories/route.factory.d.ts +6 -0
- package/dist/factories/route.factory.js +22 -0
- package/dist/imitations/fetch.imitation.d.ts +2 -0
- package/dist/imitations/fetch.imitation.js +15 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/pipes/app.pipe.d.ts +2 -0
- package/dist/pipes/app.pipe.js +45 -0
- package/dist/pipes/auth.pipe.d.ts +3 -0
- package/dist/pipes/auth.pipe.js +37 -0
- package/dist/pipes/db.pipe.d.ts +11 -0
- package/dist/pipes/db.pipe.js +31 -0
- package/dist/pipes/index.d.ts +5 -0
- package/dist/pipes/index.js +5 -0
- package/dist/pipes/json.pipe.d.ts +9 -0
- package/dist/pipes/json.pipe.js +10 -0
- package/dist/pipes/openapi.pipe.d.ts +2 -0
- package/dist/pipes/openapi.pipe.js +11 -0
- package/package.json +33 -28
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getPrivateKey } from '@/crypto';
|
|
2
|
+
import { sign } from 'hono/jwt';
|
|
3
|
+
export async function createTokenFor(user) {
|
|
4
|
+
const privateKey = await getPrivateKey();
|
|
5
|
+
const payload = {
|
|
6
|
+
sub: user.ID,
|
|
7
|
+
name: user.name,
|
|
8
|
+
role: user.role,
|
|
9
|
+
exp: createTokenExpirationInMonths(6), // token expired in 6 months
|
|
10
|
+
};
|
|
11
|
+
return await sign(payload, Buffer.from(privateKey).toString("utf8"), "HS256");
|
|
12
|
+
}
|
|
13
|
+
function createTokenExpirationInMonths(months) {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
now.setMonth(now.getMonth() + months);
|
|
16
|
+
return Math.floor(now.getTime() / 1000);
|
|
17
|
+
}
|
|
18
|
+
export function createOTP() {
|
|
19
|
+
const otp = Math.floor(Math.random() * 1000000);
|
|
20
|
+
// Mengubah angka OTP menjadi string.
|
|
21
|
+
// Menggunakan padStart(6, '0') untuk memastikan string selalu 6 digit.
|
|
22
|
+
// Jika angka kurang dari 6 digit (misal 9876), maka akan ditambahkan nol di depan (misal "009876").
|
|
23
|
+
const otpString = otp.toString().padStart(6, "0");
|
|
24
|
+
return otpString;
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "@hono/zod-openapi";
|
|
2
|
+
export declare const createListResponseSchema: <DataSchema extends z.ZodTypeAny>(args: {
|
|
3
|
+
dataSchema: DataSchema;
|
|
4
|
+
}) => z.ZodObject<{
|
|
5
|
+
sortBy: z.ZodDefault<z.ZodString>;
|
|
6
|
+
sortDir: z.ZodDefault<z.ZodEnum<{
|
|
7
|
+
asc: "asc";
|
|
8
|
+
desc: "desc";
|
|
9
|
+
}>>;
|
|
10
|
+
cursor: z.ZodString;
|
|
11
|
+
data: z.ZodArray<DataSchema>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export declare const createOkResponseSchema: () => z.ZodObject<{
|
|
14
|
+
msg: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "@hono/zod-openapi";
|
|
2
|
+
export const createListResponseSchema = (args) => z.object({
|
|
3
|
+
sortBy: z
|
|
4
|
+
.string()
|
|
5
|
+
.nonempty()
|
|
6
|
+
.default("createdAt")
|
|
7
|
+
.openapi({ examples: ["createdAt", "updatedAt"] }),
|
|
8
|
+
sortDir: z.enum(["asc", "desc"]).default("desc"),
|
|
9
|
+
cursor: z.string().datetime(),
|
|
10
|
+
data: z.array(args.dataSchema),
|
|
11
|
+
});
|
|
12
|
+
export const createOkResponseSchema = () => z.object({ msg: z.string().openapi({ example: "Query.OK" }) });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createRoute, RouteConfig } from "@hono/zod-openapi";
|
|
2
|
+
type CreateRouteConfig = Parameters<typeof createRoute>[0];
|
|
3
|
+
export declare function createSecureRoute(config: Omit<CreateRouteConfig, "request"> & {
|
|
4
|
+
request?: Omit<CreateRouteConfig["request"], "headers">;
|
|
5
|
+
}): RouteConfig;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
// Reusable auth header schema
|
|
3
|
+
const authHeader = z.object({
|
|
4
|
+
authorization: z
|
|
5
|
+
.string()
|
|
6
|
+
.startsWith("Bearer ")
|
|
7
|
+
.openapi({
|
|
8
|
+
description: "Bearer token",
|
|
9
|
+
example: !process.env?.ENV?.startsWith("prod") && process.env?.AUTH_TOKEN_TEST
|
|
10
|
+
? `Bearer ${process.env?.AUTH_TOKEN_TEST}`
|
|
11
|
+
: "Bearer <your-token>",
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
export function createSecureRoute(config) {
|
|
15
|
+
return createRoute({
|
|
16
|
+
...config,
|
|
17
|
+
request: {
|
|
18
|
+
...(config.request ?? {}),
|
|
19
|
+
headers: authHeader,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function imitateFetch(imitatedResponses) {
|
|
2
|
+
return (input, _init) => {
|
|
3
|
+
const url = new URL(typeof input === "string" ? input : input.toString());
|
|
4
|
+
const res = imitatedResponses[url.href];
|
|
5
|
+
return (res ?? new Response(`{"msg":"Imitated URL Not Found"}`, { status: 404 }));
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function setImitatedResponse(responseText) {
|
|
9
|
+
return new Response(responseText.trim(), {
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
12
|
+
},
|
|
13
|
+
status: 200,
|
|
14
|
+
});
|
|
15
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { rateLimiter } from 'hono-rate-limiter';
|
|
2
|
+
import { languageDetector } from "hono/language";
|
|
3
|
+
import { configureOpenapi } from './openapi.pipe';
|
|
4
|
+
import { configureAuthGuard } from './auth.pipe';
|
|
5
|
+
export function configureAppEssentialPipe(app, appName, appVersion) {
|
|
6
|
+
// LANGUAGE
|
|
7
|
+
configureLanguage(app);
|
|
8
|
+
// HELMET
|
|
9
|
+
configureHelmet(app);
|
|
10
|
+
// RATE LIMIT
|
|
11
|
+
configureRateLimit(app);
|
|
12
|
+
// OPENAPI
|
|
13
|
+
configureOpenapi(app, appName, appVersion);
|
|
14
|
+
// Auth Middleware
|
|
15
|
+
configureAuthGuard(app);
|
|
16
|
+
// Load unauthenticated modules
|
|
17
|
+
}
|
|
18
|
+
function configureHelmet(app) {
|
|
19
|
+
app.use("*", (c, next) => {
|
|
20
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
21
|
+
c.header("X-Frame-Options", "DENY");
|
|
22
|
+
c.header("X-XSS-Protection", "1; mode=block");
|
|
23
|
+
c.header("Referrer-Policy", "no-referrer");
|
|
24
|
+
return next();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function configureLanguage(app) {
|
|
28
|
+
app.use(languageDetector({
|
|
29
|
+
supportedLanguages: ["en", "id"],
|
|
30
|
+
fallbackLanguage: "en",
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function configureRateLimit(app) {
|
|
34
|
+
app.use("/api/*", rateLimiter({
|
|
35
|
+
windowMs: 60 * 1000, // 1 minute
|
|
36
|
+
limit: 60,
|
|
37
|
+
standardHeaders: true,
|
|
38
|
+
message: "Slowdown bud!",
|
|
39
|
+
keyGenerator: (c) => {
|
|
40
|
+
return (c.req.header("x-forwarded-for") ??
|
|
41
|
+
c.req.header("x-real-ip") ??
|
|
42
|
+
"unknown");
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getPrivateKey } from '@/crypto';
|
|
2
|
+
import { verify } from "hono/jwt";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
export function configureAuthGuard(app) {
|
|
5
|
+
app.use("/api/*", async (c, next) => {
|
|
6
|
+
const authorizationHeader = c.req.header("Authorization");
|
|
7
|
+
if (authorizationHeader) {
|
|
8
|
+
const bearerToken = authorizationHeader.split(" ")[1];
|
|
9
|
+
if (bearerToken) {
|
|
10
|
+
try {
|
|
11
|
+
const payload = await verifyToken(bearerToken);
|
|
12
|
+
c.set("jwtPayload", payload);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error(error);
|
|
16
|
+
throw new HTTPException(401, { message: "Invalid token" });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
}, async (c, next) => {
|
|
22
|
+
const payload = c.get("jwtPayload");
|
|
23
|
+
if (!payload)
|
|
24
|
+
throw new HTTPException(401, { message: "Invalid payload" });
|
|
25
|
+
c.set("user", payload);
|
|
26
|
+
await next();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function verifyToken(token) {
|
|
30
|
+
const privateKey = await getPrivateKey();
|
|
31
|
+
return await verify(token, Buffer.from(privateKey).toString("utf8"), "HS256");
|
|
32
|
+
}
|
|
33
|
+
export function authorize(allowed) {
|
|
34
|
+
if (!allowed) {
|
|
35
|
+
throw new HTTPException(401, { message: "Unauthorized" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
2
|
+
export type DatabaseResult<T> = {
|
|
3
|
+
code: ContentfulStatusCode;
|
|
4
|
+
success: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
error?: {
|
|
7
|
+
message: string;
|
|
8
|
+
} | undefined;
|
|
9
|
+
data?: Awaited<T> | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare const DB: <T>(fn: () => Promise<T>) => Promise<DatabaseResult<T>>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const DB = async (fn) => {
|
|
2
|
+
try {
|
|
3
|
+
const data = await fn();
|
|
4
|
+
if (!data) {
|
|
5
|
+
return {
|
|
6
|
+
success: false,
|
|
7
|
+
message: "Query.RecordNotFound.",
|
|
8
|
+
code: 404,
|
|
9
|
+
data,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return { success: true, message: "Query.OK", code: 200, data };
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
const maybeError = err;
|
|
16
|
+
if (maybeError?.cause?.message) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
code: 422,
|
|
20
|
+
message: "Unprocessable Entity",
|
|
21
|
+
error: { message: maybeError.cause.message },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
code: 500,
|
|
27
|
+
message: "Server error",
|
|
28
|
+
error: { message: "Unhandled Exception" },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from '@hono/zod-openapi';
|
|
2
|
+
export declare function createJsonContent(content: z.ZodObject, description: string): {
|
|
3
|
+
content: {
|
|
4
|
+
"application/json": {
|
|
5
|
+
schema: z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Scalar } from "@scalar/hono-api-reference";
|
|
2
|
+
export function configureOpenapi(app, title, version) {
|
|
3
|
+
if (!process.env.ENV?.startsWith("prod")) {
|
|
4
|
+
app
|
|
5
|
+
.doc("/openapi.json", {
|
|
6
|
+
openapi: "3.0.0",
|
|
7
|
+
info: { version, title },
|
|
8
|
+
})
|
|
9
|
+
.get("/docs", Scalar(() => ({ url: "/openapi.json", theme: "alternate" })));
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chomot",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"prepublishOnly": "npm run build",
|
|
10
|
-
"test": "vitest"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
],
|
|
15
|
-
"keywords": [],
|
|
16
|
-
"author": "smwgn1331",
|
|
17
|
-
"license": "ISC",
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
20
|
-
"@types/node": "^24.7.2",
|
|
21
|
-
"tsx": "^4.20.6",
|
|
22
|
-
"typescript": "^5.9.3",
|
|
23
|
-
"vitest": "^3.2.4"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "chomot",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"test": "vitest"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "smwgn1331",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
20
|
+
"@types/node": "^24.7.2",
|
|
21
|
+
"tsx": "^4.20.6",
|
|
22
|
+
"typescript": "^5.9.3",
|
|
23
|
+
"vitest": "^3.2.4"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@hono/node-server": "^1.19.5",
|
|
27
|
+
"@hono/zod-openapi": "^1.1.3",
|
|
28
|
+
"@scalar/hono-api-reference": "^0.9.21",
|
|
29
|
+
"hono": "^4.9.12",
|
|
30
|
+
"hono-rate-limiter": "^0.4.2",
|
|
31
|
+
"libsodium-wrappers-sumo": "^0.7.15"
|
|
32
|
+
}
|
|
33
|
+
}
|