482 lines
11 KiB
TypeScript
482 lines
11 KiB
TypeScript
import * as fastify from "fastify";
|
|
import * as mongodb from "mongodb";
|
|
import * as os from "os";
|
|
import * as url from "url";
|
|
import { numbert } from "./numbert";
|
|
import * as cryptoRandomString from "crypto-random-string";
|
|
import { Server, IncomingMessage, ServerResponse } from "http";
|
|
import * as murmurhash3 from "murmurhash3js";
|
|
process.env.PORT = "8080";
|
|
process.env.MONGO = "mongodb://127.0.0.1/numbert";
|
|
process.on("unhandledRejection", (err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
process.on("uncaughtException", (err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
namespace utils {
|
|
// export function now(): number {
|
|
// return Math.floor(Date.now() / 1000)
|
|
// }
|
|
export function getToken(request: fastify.FastifyRequest): string {
|
|
let token = request.headers["authorization"];
|
|
if (!token || token.length == 0) throw new Error("no auth header");
|
|
if (token.startsWith("Bearer"))
|
|
token = token.substr("Bearer".length).trim();
|
|
if (token.length == 0) throw new Error("no auth header");
|
|
return token;
|
|
}
|
|
export async function genToken(staticID: string): Promise<string> {
|
|
return (
|
|
(await cryptoRandomString.async({
|
|
type: "hex",
|
|
length: 155,
|
|
})) +
|
|
"." +
|
|
staticID +
|
|
"." +
|
|
Date.now().toString(16)
|
|
);
|
|
}
|
|
}
|
|
const server: fastify.FastifyInstance<
|
|
Server,
|
|
IncomingMessage,
|
|
ServerResponse
|
|
> = fastify.fastify({
|
|
logger: true,
|
|
disableRequestLogging: true,
|
|
caseSensitive: true,
|
|
connectionTimeout: 5 * 1000,
|
|
keepAliveTimeout: 1 * 1000,
|
|
maxParamLength: 5,
|
|
bodyLimit: 1 * 1000 * 1000,
|
|
onProtoPoisoning: "remove",
|
|
onConstructorPoisoning: "remove",
|
|
requestIdLogLabel: "requestId",
|
|
trustProxy: true,
|
|
pluginTimeout: 0,
|
|
return503OnClosing: true,
|
|
});
|
|
const mongoClient = new mongodb.MongoClient(process.env.MONGO, {
|
|
ignoreUndefined: true,
|
|
useUnifiedTopology: true,
|
|
useNewUrlParser: true,
|
|
serverSelectionTimeoutMS: 5 * 1000,
|
|
poolSize: os.cpus().length,
|
|
});
|
|
process.on("beforeExit", async () => {
|
|
try {
|
|
await mongoClient.close(true);
|
|
} catch (_) {}
|
|
});
|
|
let numbertApi: numbert.API;
|
|
server.setErrorHandler((error, _, reply) => {
|
|
try {
|
|
if (!error.code)
|
|
error.code = murmurhash3.x86
|
|
.hash32(error.message || error.name)
|
|
.toString();
|
|
reply.code(400).send({
|
|
error: {
|
|
code: error.code.toString(),
|
|
message: error.message,
|
|
name: error.name,
|
|
},
|
|
});
|
|
} catch (_) {}
|
|
});
|
|
server.setNotFoundHandler((_, reply) => {
|
|
reply.send(`{"error":{"code":"NOT_FOUND","message":"path is not defined"}}`);
|
|
});
|
|
server.post("/", (_, reply) => {
|
|
reply.send({ version: "0.1.0", time: (Date.now() / 1000).toString() });
|
|
});
|
|
server.post(
|
|
"/RegisterCaller",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["pwd", "dynamicID"],
|
|
properties: {
|
|
pwd: {
|
|
type: "string",
|
|
minLength: 8,
|
|
maxLength: 95,
|
|
// pattern: "/^(?=.*[A-Z])(?=.*d)[A-Za-zd]{8,}$/",
|
|
},
|
|
dynamicID: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 35,
|
|
patten: "^w+$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const pwd = request.body.pwd;
|
|
const dynamicID = request.body.dynamicID;
|
|
const { staticID } = await numbertApi.registerCaller(pwd, dynamicID);
|
|
reply.send({ staticID });
|
|
}
|
|
);
|
|
server.post(
|
|
"/NewCallerToken",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["pwd", "staticID"],
|
|
properties: {
|
|
pwd: {
|
|
type: "string",
|
|
minLength: 8,
|
|
maxLength: 95,
|
|
// pattern: "/^(?=.*[A-Z])(?=.*d)[A-Za-zd]{8,}$/",
|
|
},
|
|
staticID: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 55,
|
|
pattern: "^[a-f 0-9]{5,}$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const pwd = request.body.pwd;
|
|
const staticID = request.body.staticID;
|
|
const token = await utils.genToken(staticID);
|
|
await numbertApi.updateCallerToken(staticID, pwd, token);
|
|
reply.send({ token });
|
|
}
|
|
);
|
|
server.post("/DropCallerToken", async (request, reply) => {
|
|
const token = utils.getToken(request);
|
|
await numbertApi.dropCallerToken(token);
|
|
reply.code(202).send();
|
|
});
|
|
server.post(
|
|
"/ResetCallerPwd",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["pwd", "newPwd"],
|
|
properties: {
|
|
pwd: {
|
|
type: "string",
|
|
minLength: 8,
|
|
maxLength: 95,
|
|
//pattern: "/^(?=.*[A-Z])(?=.*d)[A-Za-zd]{8,}$/",
|
|
},
|
|
newPwd: { type: "string", minLength: 8, maxLength: 95 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const pwd = request.body.pwd;
|
|
const newPwd = request.body.newPwd;
|
|
if (pwd === newPwd) throw new Error("same passwords");
|
|
let token = utils.getToken(request);
|
|
await numbertApi.updateCallerPwd(token, pwd, newPwd);
|
|
reply.code(202).send();
|
|
}
|
|
);
|
|
server.post(
|
|
"/PatchCaller",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
properties: {
|
|
menu: {
|
|
minItems: 1,
|
|
maxLength: 70,
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
},
|
|
},
|
|
dynamicID: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 35,
|
|
patten: "^w+$",
|
|
},
|
|
title: { type: "string", minLength: 5, maxLength: 55 },
|
|
desc: { type: "string", minLength: 0, maxLength: 255 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
let token = utils.getToken(request);
|
|
numbertApi.patchCaller(token, request.body);
|
|
reply.code(202).send();
|
|
}
|
|
);
|
|
server.post(
|
|
"/ResolveCallerDynamicID",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
required: ["dynamicID"],
|
|
|
|
additionalProperties: false,
|
|
properties: {
|
|
dynamicID: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 35,
|
|
patten: "^w+$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const { staticID } = await numbertApi.resolveCallerDynamicID(
|
|
request.body.dynamicID
|
|
);
|
|
reply.send({ staticID });
|
|
}
|
|
);
|
|
|
|
server.post("/ResolveCallerToken", async (request, reply) => {
|
|
const token = utils.getToken(request);
|
|
const { staticID } = await numbertApi.resolveCallerToken(token);
|
|
reply.send({ staticID });
|
|
});
|
|
|
|
server.post(
|
|
"/GetCaller",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
required: ["staticID"],
|
|
|
|
additionalProperties: false,
|
|
properties: {
|
|
staticID: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 55,
|
|
pattern: "^[a-f 0-9]{5,}$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const caller = await numbertApi.getCaller(request.body.staticID);
|
|
reply.send(caller);
|
|
}
|
|
);
|
|
|
|
server.post(
|
|
"/UpsertCall",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
required: ["msg", "code"],
|
|
|
|
additionalProperties: false,
|
|
properties: {
|
|
msg: { type: "string", minLength: 0, maxLength: 95 },
|
|
code: {
|
|
type: "string",
|
|
minLength: 1,
|
|
maxLength: 12,
|
|
pattern: "^[0-9 a-z A-Z]+$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const token = utils.getToken(request);
|
|
await numbertApi.upsertCall(token, {
|
|
msg: request.body.msg,
|
|
code: request.body.code,
|
|
});
|
|
reply.code(202).send();
|
|
}
|
|
);
|
|
server.post(
|
|
"/ListCallees",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["filter", "skip", "limit", "sort"],
|
|
|
|
properties: {
|
|
filter: { type: "object" },
|
|
skip: { type: "number", minimum: 0 },
|
|
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
// sort: {
|
|
// type: "array",
|
|
// items: [
|
|
// {
|
|
// type: "string",
|
|
// },
|
|
// {
|
|
// type: "number",
|
|
// },
|
|
// ],
|
|
// },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const token = utils.getToken(request);
|
|
const { staticID } = await numbertApi.resolveCallerToken(token);
|
|
request.body.filter.caller = staticID;
|
|
const list = await numbertApi.listCallees(
|
|
request.body.filter,
|
|
[["callAt", -1]],
|
|
request.body.limit,
|
|
request.body.skip
|
|
);
|
|
reply.send(list);
|
|
}
|
|
);
|
|
server.post(
|
|
"/PurgeCallees",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
required: ["filter"],
|
|
|
|
additionalProperties: false,
|
|
properties: {
|
|
filter: { type: "object" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const token = utils.getToken(request);
|
|
const { staticID } = await numbertApi.resolveCallerToken(token);
|
|
request.body.filter.caller = staticID;
|
|
const { deletedCount } = await numbertApi.purgeCallees(request.body.filter);
|
|
reply.send({ deletedCount });
|
|
}
|
|
);
|
|
|
|
server.post(
|
|
"/GetCall",
|
|
{
|
|
schema: {
|
|
body: {
|
|
type: "object",
|
|
required: ["code", "caller"],
|
|
|
|
additionalProperties: false,
|
|
properties: {
|
|
code: {
|
|
type: "string",
|
|
minLength: 1,
|
|
maxLength: 12,
|
|
pattern: "^[0-9 a-z A-Z]+$",
|
|
},
|
|
caller: {
|
|
type: "string",
|
|
minLength: 5,
|
|
maxLength: 55,
|
|
pattern: "^[a-f 0-9]{5,}$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (
|
|
request: fastify.FastifyRequest<{
|
|
Body: any;
|
|
}>,
|
|
reply
|
|
) => {
|
|
const { msg, callAt } = await numbertApi.getCall(
|
|
request.body.caller,
|
|
request.body.code
|
|
);
|
|
reply.send({ msg, callAt });
|
|
}
|
|
);
|
|
|
|
mongoClient.connect().then(async (mongoClient) => {
|
|
const mongoDatabase = mongoClient.db(
|
|
url.parse(process.env.MONGO || "").pathname?.substr(1) || ""
|
|
);
|
|
await server.listen(parseInt(process.env.PORT || ""), "0.0.0.0");
|
|
numbertApi = new numbert.API(
|
|
mongoDatabase.collection("callers"),
|
|
mongoDatabase.collection("callees")
|
|
);
|
|
await numbertApi.init();
|
|
});
|