@rpcbase/server 0.72.0 → 0.75.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/admin/README.md CHANGED
@@ -1 +1 @@
1
- admin middleware that exposes models, schemas etc
1
+ admin middleware that exposes the local models, schemas etc
package/bin.js CHANGED
@@ -6,13 +6,13 @@ require("dotenv").config({path: path.join(__dirname, "./.env")})
6
6
  const yargs = require("yargs/yargs")
7
7
  const {hideBin} = require("yargs/helpers")
8
8
 
9
+ const pack = require("./package.json")
9
10
  const start_server_infrastructure = require("./cli/start_server_infrastructure")
10
11
  const build_server = require("./cli/build_server")
11
12
  const run_agent = require("./cli/run_agent")
12
13
 
13
14
  let is_command = false
14
15
 
15
-
16
16
  const args = yargs(hideBin(process.argv))
17
17
  .command("start", "runs server/infrastructure", () => {}, (args) => {
18
18
  is_command = true
@@ -22,12 +22,11 @@ const args = yargs(hideBin(process.argv))
22
22
  is_command = true
23
23
  run_agent()
24
24
  })
25
- .command("build", "build server", () => {}, (args) => {
25
+ .command("build", "Build the server package", () => {}, (args) => {
26
26
  is_command = true
27
27
  build_server()
28
28
  })
29
29
  .option("verbose", {
30
- alias: "v",
31
30
  type: "boolean",
32
31
  default: false,
33
32
  description: "Run with verbose logging"
@@ -38,6 +37,8 @@ if (!is_command) {
38
37
  console.log("server default com22mand")
39
38
  }
40
39
 
40
+
41
+ // TODO: why was this needed
41
42
  // // add helpers to ensure proper process termination
42
43
  // const exit_clean = () => {
43
44
  // process.exit()
package/cli/run_native.js CHANGED
@@ -11,6 +11,10 @@ const mkdirp = require("mkdirp")
11
11
  // TODO: verify mongod and redis-sever and elasticsearch executables are present
12
12
  // and have the right version
13
13
 
14
+ const NO_PRINT_LIST = [
15
+ // "database",
16
+ ]
17
+
14
18
  // load env
15
19
  const get_env = () => {
16
20
  const env_path = path.join(process.cwd(), "./.env")
@@ -19,17 +23,22 @@ const get_env = () => {
19
23
  return env
20
24
  }
21
25
 
22
- const init_processes = () => {
26
+ const init_processes = (args) => {
23
27
  const env = get_env()
24
28
 
25
29
  const infrastructure_conf_dir = path.join(__dirname, "../infrastructure")
26
30
 
27
- // create data dirs
31
+ // db storage
28
32
  const db_path = path.join(process.cwd(), "../infrastructure/data/database/db/")
29
- const db_logs_path = path.join(process.cwd(), "../infrastructure/data/database/log/")
30
33
 
34
+ // db log file
35
+ const db_logs_path = path.join(process.cwd(), "../infrastructure/data/database/log/logs.txt")
36
+
37
+ // session-store + worker-queue
31
38
  const session_store_dir = path.join(process.cwd(), "../infrastructure/data/session-store/data")
39
+ const session_store_logs_path = path.join(process.cwd(), "../infrastructure/data/session-store/log/logs.txt")
32
40
  const worker_queue_dir = path.join(process.cwd(), "../infrastructure/data/worker-queue/data")
41
+ const worker_queue_logs_path = path.join(process.cwd(), "../infrastructure/data/worker-queue/log/logs.txt")
33
42
 
34
43
  // TODO: add a redis cache
35
44
  const processes = [
@@ -46,14 +55,16 @@ const init_processes = () => {
46
55
  {
47
56
  name: "database",
48
57
  dirs: [
49
- db_path, db_logs_path
58
+ db_path,
59
+ path.dirname(db_logs_path),
50
60
  ],
51
61
  cmd: ["mongod", [
52
62
  "--quiet",
53
63
  "--dbpath", db_path,
54
64
  "--port", env.DATABASE_PORT,
55
65
  // "--bind_ip_all",
56
- // "--logpath", "/var/log/logs.txt",
66
+ // if in verbose mode, print to stdout, else write to file
67
+ ...(args.verbose ? [] : ["--logpath", db_logs_path]),
57
68
  "--replSet", "rs0"
58
69
  ]]
59
70
  },
@@ -62,11 +73,13 @@ const init_processes = () => {
62
73
  name: "session-store",
63
74
  dirs: [
64
75
  session_store_dir,
76
+ path.dirname(session_store_logs_path),
65
77
  ],
66
78
  cmd: ["redis-server", [
67
79
  path.join(infrastructure_conf_dir, "./redis/redis.conf"),
68
80
  "--dir", session_store_dir,
69
81
  "--port", env.SESSION_STORE_PORT,
82
+ ...(args.verbose ? [] : ["--logfile", session_store_logs_path]),
70
83
  ]]
71
84
  },
72
85
  // worker-queue
@@ -74,11 +87,13 @@ const init_processes = () => {
74
87
  name: "worker-queue",
75
88
  dirs: [
76
89
  worker_queue_dir,
90
+ path.dirname(worker_queue_logs_path),
77
91
  ],
78
92
  cmd: ["redis-server", [
79
93
  path.join(infrastructure_conf_dir, "./redis/redis.conf"),
80
94
  "--dir", worker_queue_dir,
81
95
  "--port", env.WORKER_QUEUE_PORT,
96
+ ...(args.verbose ? [] : ["--logfile", worker_queue_logs_path]),
82
97
  ]]
83
98
  },
84
99
  ]
@@ -118,22 +133,32 @@ const init_processes = () => {
118
133
 
119
134
  // TODO: run each native process from list
120
135
  // TODO: handle process close
121
- const run_native = (infrastructure_dir, proj_prefix) => {
122
- console.log("running in NATIVE mode")
136
+ const run_native = (infrastructure_dir, proj_prefix, args) => {
137
+ console.log(`running in ${colors.bold(colors.magenta("NATIVE"))} mode`)
123
138
 
124
- const processes = init_processes()
139
+ const processes = init_processes(args)
125
140
 
126
141
  const run_process = (item) => {
127
- // console.log("start process", item)
142
+ // console.log("run_native:run_process:item", item)
128
143
  const ps = spawn(item.cmd[0], item.cmd[1], {env: {...process.env, CONTAINER_MODE: "native"}})
129
144
 
130
- ps.stdout.on("data", (data) => {
131
- process.stdout.write(`${colors.green(item.name)}: ${data.toString()}`)
132
- })
145
+ // TODO: add flag to force print
146
+ if (!NO_PRINT_LIST.includes(item.name)) {
133
147
 
134
- ps.stderr.on("data", (data) => {
135
- process.stderr.write(`${item.name}: ${data.toString()}`)
136
- })
148
+ let prefix = ""
149
+ // avoid printing [server] server:
150
+ if (!args.verbose && item.name !== "server") {
151
+ prefix = `${colors.green(item.name)}: `
152
+ }
153
+
154
+ ps.stdout.on("data", (data) => {
155
+ process.stdout.write(`${prefix}${data.toString()}`)
156
+ })
157
+
158
+ ps.stderr.on("data", (data) => {
159
+ process.stderr.write(`${prefix}${data.toString()}`)
160
+ })
161
+ }
137
162
  }
138
163
 
139
164
  const spawned = processes.map(run_process)
@@ -19,9 +19,9 @@ const start_server = async(args) => {
19
19
  const proj_prefix = path.basename(proj_parent_dir)
20
20
 
21
21
  if (runtime === "docker") {
22
- run_docker(infrastructure_dir, proj_prefix)
22
+ run_docker(infrastructure_dir, proj_prefix, args)
23
23
  } else if (runtime === "native") {
24
- run_native(infrastructure_dir, proj_prefix)
24
+ run_native(infrastructure_dir, proj_prefix, args)
25
25
  } else {
26
26
  throw new Error("unknown runtime")
27
27
  }
package/database.js CHANGED
@@ -24,6 +24,7 @@ module.exports = async() => {
24
24
  })
25
25
 
26
26
  mongoose.connection.on("disconnected", (arg) => {
27
+ // TODO: add tracing
27
28
  console.log("Mongoose disconnected", arg)
28
29
  })
29
30
 
@@ -1,10 +1,10 @@
1
1
  /* @flow */
2
+ // TODO: replace console debug
2
3
 
3
- const is_production = process.env.NODE_ENV === "production"
4
4
 
5
+ const is_production = process.env.NODE_ENV === "production"
5
6
 
6
7
  module.exports = (app) => {
7
-
8
8
  app.post("/api/__dev_save_coverage", (req, res) => {
9
9
  console.log("sending coverage for", req.body.path_key)
10
10
  res.json(global.__coverage__)
package/express/index.js CHANGED
@@ -3,9 +3,13 @@ const cors = require("cors")
3
3
  const express = require("express")
4
4
  const body_parser = require("body-parser")
5
5
 
6
+ // functionality middlewares
7
+ const auth = require("../src/auth")
8
+
6
9
  const dev_save_coverage = require("./dev_save_coverage")
7
10
  const session_middleware = require("./session_middleware")
8
11
 
12
+
9
13
  const is_production = process.env.IS_PRODUCTION === "yes"
10
14
  const {APP_DOMAIN, CLIENT_PORT} = process.env
11
15
 
@@ -57,6 +61,7 @@ module.exports = () => {
57
61
  app.get("/api/ping", (req, res) => res.json({message: "pong"}))
58
62
  app.post("/api/ping", (req, res) => res.json({message: "pong"}))
59
63
 
64
+ auth(app)
60
65
  dev_save_coverage(app)
61
66
 
62
67
  return app
@@ -58,9 +58,14 @@ setTimeout(async() => {
58
58
  store: new redis_store({client: redis_client}),
59
59
  proxy: true,
60
60
  saveUninitialized: false,
61
+ // WARNING
61
62
  // TODO: use session secret from env
62
63
  secret: "session secret wowow",
63
64
  resave: false,
65
+ cookie: {
66
+ // TODO: test this
67
+ maxAge: 1000 * 3600 * 24 * 100 // 100 days
68
+ }
64
69
  })
65
70
 
66
71
  // TODO: is this still necessary
package/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /* @flow */
2
- const client_router = require("./client/client_router")
3
2
  const database = require("./database")
4
3
  const express = require("./express")
5
- const rpc_router = require("./rpc/rpc_router")
4
+ const client_router = require("./src/client/client_router")
5
+ const rpc_router = require("./src/rpc/rpc_router")
6
6
 
7
7
  module.exports = {
8
- client_router,
9
8
  database,
10
9
  express,
10
+ client_router,
11
11
  rpc_router,
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/server",
3
- "version": "0.72.0",
3
+ "version": "0.75.0",
4
4
  "license": "SSPL-1.0",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "test": "echo \"Error: no test specified\" && exit 0"
11
11
  },
12
12
  "dependencies": {
13
- "@rpcbase/agent": "0.8.0",
13
+ "@rpcbase/agent": "0.9.0",
14
+ "@rpcbase/std": "0.3.0",
14
15
  "body-parser": "1.20.0",
15
16
  "connect-redis": "6.1.3",
16
17
  "cors": "2.8.5",
@@ -19,7 +20,7 @@
19
20
  "express": "4.18.1",
20
21
  "express-session": "1.17.3",
21
22
  "glob": "8.0.3",
22
- "mongoose": "6.3.6",
23
+ "mongoose": "6.4.0",
23
24
  "picocolors": "1.0.0",
24
25
  "postmark": "3.0.11",
25
26
  "redis": "4.1.0",
@@ -0,0 +1,15 @@
1
+ /* @flow */
2
+
3
+ const check_session = async(payload, ctx) => {
4
+ const {req} = ctx
5
+ // console.log("session check", req.session)
6
+
7
+ const is_signed_in = !!req.session.user_id
8
+ return {
9
+ status: "ok",
10
+ is_signed_in,
11
+ user_id: req.session.user_id,
12
+ }
13
+ }
14
+
15
+ module.exports = check_session
@@ -0,0 +1,35 @@
1
+ /* @flow */
2
+ const async_wrapper = require("../helpers/async_wrapper")
3
+
4
+ const sign_up = require("./sign_up")
5
+ const sign_in = require("./sign_in")
6
+ const sign_out = require("./sign_out")
7
+ const check_session = require("./check_session")
8
+
9
+ const sign_up_handler = async(req, res) => {
10
+ const result = await sign_up(req.body, {req})
11
+ res.json(result)
12
+ }
13
+
14
+ const sign_in_handler = async(req, res) => {
15
+ const result = await sign_in(req.body, {req})
16
+ res.json(result)
17
+ }
18
+
19
+ const sign_out_handler = async(req, res) => {
20
+ const result = await sign_out(req.body, {req})
21
+ res.json(result)
22
+ }
23
+
24
+ const check_session_handler = async(req, res) => {
25
+ const result = await check_session(req.body, {req})
26
+ res.json(result)
27
+ }
28
+
29
+ module.exports = (app) => {
30
+ app.post("/api/v1/auth/sign_up", async_wrapper(sign_up_handler))
31
+ app.post("/api/v1/auth/sign_in", async_wrapper(sign_in_handler))
32
+ app.post("/api/v1/auth/sign_out", async_wrapper(sign_out_handler))
33
+
34
+ app.post("/api/v1/auth/check_session", async_wrapper(check_session_handler))
35
+ }
@@ -0,0 +1,44 @@
1
+ /* @flow */
2
+ const {compare_hash} = require("@rpcbase/std/crypto/hash")
3
+
4
+ const mongoose = require("../../mongoose")
5
+
6
+ const fail = () => ({
7
+ errors: {form: "Invalid email or password"}
8
+ })
9
+
10
+ const sign_in = async({email, password}, ctx) => {
11
+ const User = mongoose.model("User")
12
+
13
+ const {req} = ctx
14
+
15
+ // find the matching user
16
+ // TODO: document ctx param
17
+ // const user = await User.findOne({email}, null, {ctx})
18
+ const user = await User.findOne({email}, null)
19
+
20
+ // console.log("ICII2222", email, password, user)
21
+
22
+ if (!user) {
23
+ return fail()
24
+ }
25
+
26
+ const hashed_pass = user.password_hash
27
+
28
+ // TODO: fix broken bcrypt bins in docker
29
+ // const is_match = await bcrypt.compare(password, hashed_pass)
30
+ console.warn("warning: skipping bcrypt hash check")
31
+ const is_match = await compare_hash(password, hashed_pass)
32
+
33
+ if (is_match) {
34
+ req.session.user_id = user._id.toString()
35
+ await req.session.save()
36
+ return {
37
+ status: "ok"
38
+ }
39
+ } else {
40
+ return fail()
41
+ }
42
+ }
43
+
44
+ module.exports = sign_in
@@ -0,0 +1,11 @@
1
+ /* @flow */
2
+
3
+ const sign_out = async(payload, ctx) => {
4
+ const {req} = ctx
5
+ await req.session.destroy()
6
+ return {
7
+ status: "ok"
8
+ }
9
+ }
10
+
11
+ module.exports = sign_out
@@ -0,0 +1,57 @@
1
+ /* @flow */
2
+ const {hash_password} = require("@rpcbase/std/crypto/hash")
3
+
4
+ const mongoose = require("../../mongoose")
5
+
6
+ const sign_up = async({email, password}, ctx) => {
7
+ const User = mongoose.model("User")
8
+ const Invite = mongoose.model("Invite")
9
+
10
+ const {req} = ctx
11
+
12
+ // check if the user already exists
13
+ const existing_user = await User.findOne({email}, null, {ctx})
14
+
15
+ if (existing_user) {
16
+ return {
17
+ status: "error",
18
+ message: "User already exists"
19
+ }
20
+ }
21
+
22
+ // check if we have an invite for this user
23
+ const invite = await Invite.findOne({email}, null, {ctx})
24
+ if (invite && !invite.is_ready) {
25
+ console.log("found an invite, but not ready", email)
26
+ return {
27
+ status: "error",
28
+ message: "Your invite is still pending approval. Expect an email in the next weeks to activate your account."
29
+ }
30
+ } else if (!invite) {
31
+ console.log("no invite for signup email:", email)
32
+ return {
33
+ status: "error",
34
+ message: "No valid invite was found for this email"
35
+ }
36
+ }
37
+
38
+ const hash = await hash_password(password)
39
+
40
+ const user = new User({
41
+ email,
42
+ password_hash: hash
43
+ })
44
+
45
+ // sign the user in
46
+ req.session.user_id = user._id.toString()
47
+
48
+ await req.session.save()
49
+ await user.save({ctx})
50
+
51
+ return {
52
+ status: "ok",
53
+ user_id: user._id
54
+ }
55
+ }
56
+
57
+ module.exports = sign_up
@@ -0,0 +1,2 @@
1
+ client router serves the client from the server
2
+ in production the client could be served from the CDN, but the server is also suitable
@@ -3,6 +3,8 @@ const fs = require("fs")
3
3
  const path = require("path")
4
4
  const glob = require("glob")
5
5
 
6
+ const async_wrapper = require("../helpers/async_wrapper")
7
+
6
8
  const src_path = path.join(process.cwd(), "./src/")
7
9
  const build_dir = path.join(process.cwd(), "build/")
8
10
  const client_build_dir = path.join(build_dir, "./client")
@@ -10,11 +12,6 @@ const client_build_dir = path.join(build_dir, "./client")
10
12
 
11
13
  // TODO: add build time static assets compression
12
14
 
13
- const async_wrapper = fn => (req, res, next) => {
14
- Promise.resolve(fn(req, res, next))
15
- .catch(next)
16
- }
17
-
18
15
  const get_client_routes = () => {
19
16
  const client_files = glob.sync(path.join(client_build_dir, "./**/*"))
20
17
  const routes = client_files
@@ -0,0 +1,8 @@
1
+ /* @flow */
2
+
3
+ const async_wrapper = fn => (req, res, next) => {
4
+ Promise.resolve(fn(req, res, next))
5
+ .catch(next)
6
+ }
7
+
8
+ module.exports = async_wrapper
@@ -4,16 +4,12 @@ const path = require("path")
4
4
  const glob = require("glob")
5
5
  const debug = require("debug")
6
6
 
7
+ const async_wrapper = require("../helpers/async_wrapper")
8
+
7
9
  const src_path = path.join(process.cwd(), "./src/")
8
10
  const build_dir = path.join(process.cwd(), "build/")
9
11
 
10
12
 
11
- const async_wrapper = fn => (req, res, next) => {
12
- Promise.resolve(fn(req, res, next))
13
- .catch(next)
14
- }
15
-
16
-
17
13
  const rpc_router = (app) => {
18
14
  const rpc_routes = glob.sync(path.join(build_dir, "./rpc/*"))
19
15
 
package/crypto/index.js DELETED
@@ -1,45 +0,0 @@
1
- /* @flow */
2
- const crypto = require("crypto")
3
-
4
- const ALGORITHM = "aes-256-cbc"
5
- const {CRYPTO_SECRET} = process.env
6
-
7
- if (!CRYPTO_SECRET || CRYPTO_SECRET === "") {
8
- throw new Error("CRYPTO_SECRET not found in env")
9
- }
10
-
11
- const key = crypto.createHash("sha256")
12
- .update(String(CRYPTO_SECRET))
13
- .digest()
14
-
15
-
16
- const encrypt = (text) => {
17
- const iv = crypto.randomBytes(16)
18
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
19
-
20
- const encrypted = Buffer.concat([
21
- cipher.update(text),
22
- cipher.final()
23
- ])
24
-
25
- return {
26
- iv: iv.toString("hex"),
27
- content: encrypted.toString("hex")
28
- }
29
- }
30
-
31
- const decrypt = (hash) => {
32
- const decipher = crypto.createDecipheriv(
33
- ALGORITHM, key, Buffer.from(hash.iv, "hex")
34
- )
35
-
36
- const decrpyted = Buffer.concat([
37
- decipher.update(Buffer.from(hash.content, "hex")),
38
- decipher.final(),
39
- ])
40
-
41
- return decrpyted.toString()
42
- }
43
-
44
-
45
- module.exports = {encrypt, decrypt}