filesystem routing
This commit is contained in:
parent
10e31e2c2f
commit
9157d247ea
19 changed files with 348 additions and 310 deletions
|
@ -1,16 +1,16 @@
|
||||||
// Identity. Store your memories and mental belongings
|
// Identity. Store your memories and mental belongings
|
||||||
// Copyright (C) 2024 Sofía Aritz
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU Affero General Public License for more details.
|
// GNU Affero General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
@ -18,4 +18,4 @@ import Fastify from "fastify";
|
||||||
|
|
||||||
export default Fastify({
|
export default Fastify({
|
||||||
logger: true,
|
logger: true,
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,28 +1,31 @@
|
||||||
// Identity. Store your memories and mental belongings
|
// Identity. Store your memories and mental belongings
|
||||||
// Copyright (C) 2024 Sofía Aritz
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU Affero General Public License for more details.
|
// GNU Affero General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import "dotenv/config"
|
import "dotenv/config";
|
||||||
import app from "./app.js";
|
import app from "./app.js";
|
||||||
|
|
||||||
const REQUIRED_VARS = ["IDENTITY_API_JWT_SECRET", "IDENTITY_API_ASSET_API_ENDPOINT", "IDENTITY_API_JWT_ALG"];
|
const REQUIRED_VARS = ["IDENTITY_API_JWT_SECRET", "IDENTITY_API_ASSET_API_ENDPOINT", "IDENTITY_API_JWT_ALG"];
|
||||||
|
|
||||||
REQUIRED_VARS.forEach(element => {
|
REQUIRED_VARS.forEach((element) => {
|
||||||
if (process.env[element] == null || (typeof process.env[element] === "string" && process.env[element].length === 0)) {
|
if (
|
||||||
|
process.env[element] == null ||
|
||||||
|
(typeof process.env[element] === "string" && process.env[element].length === 0)
|
||||||
|
) {
|
||||||
app.log.error(`Required environment variable was not set: ${element}`);
|
app.log.error(`Required environment variable was not set: ${element}`);
|
||||||
app.close().then(() => process.exit(1))
|
app.close().then(() => process.exit(1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,4 +34,5 @@ export const JWT_SECRET = new TextEncoder().encode(process.env["IDENTITY_API_JWT
|
||||||
export const JWT_ALG = process.env["IDENTITY_API_JWT_ALG"];
|
export const JWT_ALG = process.env["IDENTITY_API_JWT_ALG"];
|
||||||
export const LISTEN_PORT = process.env["IDENTITY_API_LISTEN_PORT"] || 3000;
|
export const LISTEN_PORT = process.env["IDENTITY_API_LISTEN_PORT"] || 3000;
|
||||||
export const ASSET_API_ENDPOINT = process.env["IDENTITY_API_ASSET_API_ENDPOINT"];
|
export const ASSET_API_ENDPOINT = process.env["IDENTITY_API_ASSET_API_ENDPOINT"];
|
||||||
export const ASSET_API_M2M_REFRESH_INTERVAL = process.env["IDENTITY_API_ASSET_API_M2M_REFRESH_INTERVAL_MS"] || 60 * 1000
|
export const ASSET_API_M2M_REFRESH_INTERVAL =
|
||||||
|
process.env["IDENTITY_API_ASSET_API_M2M_REFRESH_INTERVAL_MS"] || 60 * 1000;
|
||||||
|
|
|
@ -1,304 +1,45 @@
|
||||||
// Identity. Store your memories and mental belongings
|
// Identity. Store your memories and mental belongings
|
||||||
// Copyright (C) 2024 Sofía Aritz
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU Affero General Public License for more details.
|
// GNU Affero General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import app from "./app.js"
|
import app from "./app.js";
|
||||||
import { ASSET_API_ENDPOINT, IDENTITY_API_LANDING_MESSAGE, LISTEN_PORT } from "./consts.js"
|
import { IDENTITY_API_LANDING_MESSAGE, LISTEN_PORT } from "./consts.js";
|
||||||
import { contentFromSigned, verifySignature } from "./m2m.js";
|
|
||||||
import { startAuth } from "./auth.js";
|
import { startAuth } from "./auth.js";
|
||||||
import { randomUUID } from "node:crypto";
|
|
||||||
import cors from "@fastify/cors";
|
import cors from "@fastify/cors";
|
||||||
|
import { registerRoutes } from "./routes/index.js";
|
||||||
|
|
||||||
let auth = await startAuth();
|
let auth = await startAuth();
|
||||||
|
|
||||||
|
app.addSchema({
|
||||||
|
$id: "schema://identity/authorization",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
authorization: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["authorization"],
|
||||||
|
});
|
||||||
|
|
||||||
app.register(cors, {
|
app.register(cors, {
|
||||||
origin: true,
|
origin: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
registerRoutes(app, auth);
|
||||||
|
|
||||||
app.get("/", async () => {
|
app.get("/", async () => {
|
||||||
return IDENTITY_API_LANDING_MESSAGE;
|
return IDENTITY_API_LANDING_MESSAGE;
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put("/m2m/asset", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
if (!verifySignature(request.body)) {
|
|
||||||
reply.statusCode(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = JSON.parse(contentFromSigned(request.body));
|
|
||||||
|
|
||||||
let user = await auth.findUserBySessionKey(body.session_key)
|
|
||||||
user.assets.push(body.asset_id);
|
|
||||||
|
|
||||||
await auth.updateUser(user.uid, user);
|
|
||||||
app.log.info((await auth.findUserBySessionKey(body.session_key)).assets);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/m2m/account", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
if (!verifySignature(request.body)) {
|
|
||||||
reply.statusCode(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = JSON.parse(contentFromSigned(request.body));
|
|
||||||
|
|
||||||
let user = await auth.findUserBySessionKey(body.session_key)
|
|
||||||
user.password = undefined;
|
|
||||||
user.entries = undefined;
|
|
||||||
|
|
||||||
return user;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/asset/endpoint", {
|
|
||||||
async handler() {
|
|
||||||
return ASSET_API_ENDPOINT
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.delete("/entry", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
let user = await auth.user(payload.uid);
|
|
||||||
user.entries = user.entries.filter(v => v.id !== request.query.entry_id);
|
|
||||||
|
|
||||||
await auth.updateUser(payload.uid, user);
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entry_id: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["entry_id"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
app.put("/entry", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
let user = await auth.user(payload.uid);
|
|
||||||
request.body.entry.id = randomUUID().toString();
|
|
||||||
user.entries = [request.body.entry, ...user.entries];
|
|
||||||
|
|
||||||
await auth.updateUser(payload.uid, user);
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entry: { type: "object" },
|
|
||||||
},
|
|
||||||
required: ["entry"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get("/entry/list", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
if (request.query.offset < 0 || request.query.limit <= 0) {
|
|
||||||
reply.status(400);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
let user = await auth.user(payload.uid);
|
|
||||||
return user.entries.slice(request.query.offset, request.query.offset + request.query.limit);
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
limit: { type: "number" },
|
|
||||||
offset: { type: "number" },
|
|
||||||
},
|
|
||||||
required: ["limit", "offset"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post("/auth/heirs", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
if (payload.uid == null) {
|
|
||||||
reply.status(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await auth.user(payload.uid);
|
|
||||||
user.heirs = request.body;
|
|
||||||
|
|
||||||
await auth.updateUser(payload.uid, user);
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
type: "array",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get("/auth/genkey", {
|
|
||||||
async handler(request) {
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
let session_key = await auth.createSessionKey(payload.uid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
session_key,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/auth/account", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
|
||||||
let { payload } = await auth.verifyJwt(jwt);
|
|
||||||
|
|
||||||
if (payload.uid == null) {
|
|
||||||
reply.status(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await auth.user(payload.uid);
|
|
||||||
user.password = undefined;
|
|
||||||
user.entries = undefined;
|
|
||||||
return user;
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
headers: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
authorization: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["authorization"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/auth/login", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
let user = await auth.findUserByEmail(request.body.email);
|
|
||||||
|
|
||||||
if (user != null && user.password == request.body.password) {
|
|
||||||
let token = await auth.createJwt(user.uid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
token,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.code(400);
|
|
||||||
return {
|
|
||||||
error: "invalid credentials",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
email: { type: "string" },
|
|
||||||
password: { type: "string" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["email", "password"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/auth/register", {
|
|
||||||
async handler(request, reply) {
|
|
||||||
if (await auth.findUserByEmail(request.body.email) == null) {
|
|
||||||
let user = await auth.addUser({
|
|
||||||
email: request.body.email,
|
|
||||||
password: request.body.password,
|
|
||||||
name: request.body.name,
|
|
||||||
assets: [],
|
|
||||||
limits: {
|
|
||||||
assetCount: 5,
|
|
||||||
},
|
|
||||||
entries: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
return { token: await auth.createJwt(user.uid) };
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.code(400);
|
|
||||||
return {
|
|
||||||
error: "invalid data",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
name: { type: "string" },
|
|
||||||
email: { type: "string" },
|
|
||||||
password: { type: "string" },
|
|
||||||
},
|
|
||||||
required: ["name", "email", "password"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen({ port: LISTEN_PORT });
|
app.listen({ port: LISTEN_PORT });
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
// Identity. Store your memories and mental belongings
|
// Identity. Store your memories and mental belongings
|
||||||
// Copyright (C) 2024 Sofía Aritz
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU Affero General Public License for more details.
|
// GNU Affero General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { createVerify } from "node:crypto";
|
import { createVerify } from "node:crypto";
|
||||||
import assert from "node:assert"
|
import assert from "node:assert";
|
||||||
import app from "./app.js";
|
import app from "./app.js";
|
||||||
import { ASSET_API_ENDPOINT, ASSET_API_M2M_REFRESH_INTERVAL } from "./consts.js";
|
import { ASSET_API_ENDPOINT, ASSET_API_M2M_REFRESH_INTERVAL } from "./consts.js";
|
||||||
|
|
||||||
let assetPubKey = await fetchAssetPubkey()
|
let assetPubKey = await fetchAssetPubkey();
|
||||||
let assetAlgorithm = await fetchAssetAlgorithm()
|
let assetAlgorithm = await fetchAssetAlgorithm();
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -29,7 +29,7 @@ setInterval(async () => {
|
||||||
|
|
||||||
if (pubkey != null && algo != null) {
|
if (pubkey != null && algo != null) {
|
||||||
if (assetPubKey !== pubkey) {
|
if (assetPubKey !== pubkey) {
|
||||||
app.log.warn("The M2M public key has changed!")
|
app.log.warn("The M2M public key has changed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assetAlgorithm !== algo) {
|
if (assetAlgorithm !== algo) {
|
||||||
|
@ -46,7 +46,7 @@ setInterval(async () => {
|
||||||
app.log.warn("Failed to update the M2M credentials");
|
app.log.warn("Failed to update the M2M credentials");
|
||||||
app.log.warn(e);
|
app.log.warn(e);
|
||||||
}
|
}
|
||||||
}, ASSET_API_M2M_REFRESH_INTERVAL)
|
}, ASSET_API_M2M_REFRESH_INTERVAL);
|
||||||
|
|
||||||
async function fetchAssetPubkey() {
|
async function fetchAssetPubkey() {
|
||||||
let url = new URL(ASSET_API_ENDPOINT);
|
let url = new URL(ASSET_API_ENDPOINT);
|
||||||
|
@ -72,11 +72,11 @@ function partsFromSigned(content) {
|
||||||
|
|
||||||
assert(parts.length === 2);
|
assert(parts.length === 2);
|
||||||
|
|
||||||
return parts
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifySignature(content) {
|
export function verifySignature(content) {
|
||||||
let parts = partsFromSigned(content)
|
let parts = partsFromSigned(content);
|
||||||
|
|
||||||
let verify = createVerify(assetAlgorithm);
|
let verify = createVerify(assetAlgorithm);
|
||||||
verify.update(parts[0]);
|
verify.update(parts[0]);
|
||||||
|
@ -89,4 +89,4 @@ export function verifySignature(content) {
|
||||||
|
|
||||||
export function contentFromSigned(content) {
|
export function contentFromSigned(content) {
|
||||||
return partsFromSigned(content)[0];
|
return partsFromSigned(content)[0];
|
||||||
}
|
}
|
||||||
|
|
9
identity-api/src/routes/asset/endpoint.js
Normal file
9
identity-api/src/routes/asset/endpoint.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { ASSET_API_ENDPOINT } from "../../consts.js";
|
||||||
|
|
||||||
|
export default function register(app) {
|
||||||
|
app.get("/asset/endpoint", {
|
||||||
|
async handler() {
|
||||||
|
return ASSET_API_ENDPOINT;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
5
identity-api/src/routes/asset/index.js
Normal file
5
identity-api/src/routes/asset/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import endpoint from "./endpoint.js";
|
||||||
|
|
||||||
|
export default function registerRoutes(app, auth) {
|
||||||
|
endpoint(app, auth);
|
||||||
|
}
|
21
identity-api/src/routes/auth/account.js
Normal file
21
identity-api/src/routes/auth/account.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.get("/auth/account", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
if (payload.uid == null) {
|
||||||
|
reply.status(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = await auth.user(payload.uid);
|
||||||
|
user.password = undefined;
|
||||||
|
user.entries = undefined;
|
||||||
|
return user;
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
17
identity-api/src/routes/auth/genkey.js
Normal file
17
identity-api/src/routes/auth/genkey.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.get("/auth/genkey", {
|
||||||
|
async handler(request) {
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
let session_key = await auth.createSessionKey(payload.uid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
session_key,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
24
identity-api/src/routes/auth/heirs.js
Normal file
24
identity-api/src/routes/auth/heirs.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.post("/auth/heirs", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
if (payload.uid == null) {
|
||||||
|
reply.status(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = await auth.user(payload.uid);
|
||||||
|
user.heirs = request.body;
|
||||||
|
|
||||||
|
await auth.updateUser(payload.uid, user);
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
body: {
|
||||||
|
type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
13
identity-api/src/routes/auth/index.js
Normal file
13
identity-api/src/routes/auth/index.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import account from "./account.js";
|
||||||
|
import genkey from "./genkey.js";
|
||||||
|
import heirs from "./heirs.js";
|
||||||
|
import login from "./login.js";
|
||||||
|
import register from "./register.js";
|
||||||
|
|
||||||
|
export default function registerRoutes(app, auth) {
|
||||||
|
account(app, auth);
|
||||||
|
genkey(app, auth);
|
||||||
|
heirs(app, auth);
|
||||||
|
login(app, auth);
|
||||||
|
register(app, auth);
|
||||||
|
}
|
30
identity-api/src/routes/auth/login.js
Normal file
30
identity-api/src/routes/auth/login.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.post("/auth/login", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
let user = await auth.findUserByEmail(request.body.email);
|
||||||
|
|
||||||
|
if (user != null && user.password == request.body.password) {
|
||||||
|
let token = await auth.createJwt(user.uid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.code(400);
|
||||||
|
return {
|
||||||
|
error: "invalid credentials",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
email: { type: "string" },
|
||||||
|
password: { type: "string" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["email", "password"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
36
identity-api/src/routes/auth/register.js
Normal file
36
identity-api/src/routes/auth/register.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.post("/auth/register", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
if ((await auth.findUserByEmail(request.body.email)) == null) {
|
||||||
|
let user = await auth.addUser({
|
||||||
|
email: request.body.email,
|
||||||
|
password: request.body.password,
|
||||||
|
name: request.body.name,
|
||||||
|
assets: [],
|
||||||
|
limits: {
|
||||||
|
assetCount: 5,
|
||||||
|
},
|
||||||
|
entries: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { token: await auth.createJwt(user.uid) };
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.code(400);
|
||||||
|
return {
|
||||||
|
error: "invalid data",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: { type: "string" },
|
||||||
|
email: { type: "string" },
|
||||||
|
password: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["name", "email", "password"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
51
identity-api/src/routes/entry/index.js
Normal file
51
identity-api/src/routes/entry/index.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import list from "./list.js";
|
||||||
|
|
||||||
|
export default function registerRoutes(app, auth) {
|
||||||
|
list(app, auth);
|
||||||
|
|
||||||
|
app.delete("/entry", {
|
||||||
|
async handler(request) {
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
let user = await auth.user(payload.uid);
|
||||||
|
user.entries = user.entries.filter((v) => v.id !== request.query.entry_id);
|
||||||
|
|
||||||
|
await auth.updateUser(payload.uid, user);
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
query: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entry_id: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["entry_id"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put("/entry", {
|
||||||
|
async handler(request) {
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
let user = await auth.user(payload.uid);
|
||||||
|
request.body.entry.id = randomUUID().toString();
|
||||||
|
user.entries = [request.body.entry, ...user.entries];
|
||||||
|
|
||||||
|
await auth.updateUser(payload.uid, user);
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
body: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entry: { type: "object" },
|
||||||
|
},
|
||||||
|
required: ["entry"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
27
identity-api/src/routes/entry/list.js
Normal file
27
identity-api/src/routes/entry/list.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.get("/entry/list", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
if (request.query.offset < 0 || request.query.limit <= 0) {
|
||||||
|
reply.status(400);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||||
|
let { payload } = await auth.verifyJwt(jwt);
|
||||||
|
|
||||||
|
let user = await auth.user(payload.uid);
|
||||||
|
return user.entries.slice(request.query.offset, request.query.offset + request.query.limit);
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
headers: { $ref: "schema://identity/authorization" },
|
||||||
|
query: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
limit: { type: "number" },
|
||||||
|
offset: { type: "number" },
|
||||||
|
},
|
||||||
|
required: ["limit", "offset"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
11
identity-api/src/routes/index.js
Normal file
11
identity-api/src/routes/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import m2m from "./m2m/index.js";
|
||||||
|
import asset from "./asset/index.js";
|
||||||
|
import entry from "./entry/index.js";
|
||||||
|
import authRoutes from "./auth/index.js";
|
||||||
|
|
||||||
|
export function registerRoutes(app, auth) {
|
||||||
|
m2m(app, auth);
|
||||||
|
asset(app, auth);
|
||||||
|
entry(app, auth);
|
||||||
|
authRoutes(app, auth);
|
||||||
|
}
|
20
identity-api/src/routes/m2m/account.js
Normal file
20
identity-api/src/routes/m2m/account.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { contentFromSigned, verifySignature } from "../../m2m.js";
|
||||||
|
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.post("/m2m/account", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
if (!verifySignature(request.body)) {
|
||||||
|
reply.statusCode(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = JSON.parse(contentFromSigned(request.body));
|
||||||
|
|
||||||
|
let user = await auth.findUserBySessionKey(body.session_key);
|
||||||
|
user.password = undefined;
|
||||||
|
user.entries = undefined;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
20
identity-api/src/routes/m2m/asset.js
Normal file
20
identity-api/src/routes/m2m/asset.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { contentFromSigned, verifySignature } from "../../m2m.js";
|
||||||
|
|
||||||
|
export default function register(app, auth) {
|
||||||
|
app.put("/m2m/asset", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
if (!verifySignature(request.body)) {
|
||||||
|
reply.statusCode(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = JSON.parse(contentFromSigned(request.body));
|
||||||
|
|
||||||
|
let user = await auth.findUserBySessionKey(body.session_key);
|
||||||
|
user.assets.push(body.asset_id);
|
||||||
|
|
||||||
|
await auth.updateUser(user.uid, user);
|
||||||
|
app.log.info((await auth.findUserBySessionKey(body.session_key)).assets);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
7
identity-api/src/routes/m2m/index.js
Normal file
7
identity-api/src/routes/m2m/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import asset from "./asset.js";
|
||||||
|
import account from "./account.js";
|
||||||
|
|
||||||
|
export default function registerRoutes(app, auth) {
|
||||||
|
asset(app, auth);
|
||||||
|
account(app, auth);
|
||||||
|
}
|
Loading…
Reference in a new issue