@prosopo/client-example-server 2.7.56 → 2.7.90

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/env.test ADDED
@@ -0,0 +1,10 @@
1
+ PROSOPO_VERIFY_ENDPOINT=https://localhost:9235/test/siteverify
2
+ PROSOPO_VERIFICATION_TYPE=api
3
+ PROSOPO_SITE_KEY=5GCP69mhanZqJqnTvaaGvJCJZWSahz6xE2c7bTGETqSB5KDK
4
+ PROSOPO_WEB2=true
5
+ PROSOPO_SERVER_URL=https://localhost
6
+ PROSOPO_SITE_PRIVATE_KEY=bottom drive obey lake curtain smoke basket hold race lonely fit walk
7
+ PROSOPO_SERVER_PORT=9228
8
+ PROSOPO_DEFAULT_ENVIRONMENT=test
9
+ PROSOPO_MONGO_EVENTS_URI=mongodb+srv://<MONGO_URI_HERE>/frictionless_events
10
+ _DEV_ONLY_WATCH_EVENTS=false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/client-example-server",
3
- "version": "2.7.56",
3
+ "version": "2.7.90",
4
4
  "description": "Backend for client-example",
5
5
  "main": "dist/app.js",
6
6
  "types": "dist/app.d.ts",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "scripts": {
20
20
  "dev": "NODE_ENV=${NODE_ENV:-development}; vite serve --mode=$NODE_ENV --config ./vite.config.ts --host",
21
- "start": "node ./dist/app.js",
21
+ "start": "NODE_ENV=${NODE_ENV:-development}; node ./dist/app.js",
22
22
  "start:bundle": "node ./dist/bundle/prosopo_client_example_server.app.bundle.js",
23
23
  "build": "npm run build:cross-env -- --mode ${NODE_ENV:-development}",
24
24
  "build:cross-env": "vite build --config vite.esm.config.ts",
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "git+https://github.com/prosopo/client-example-server.git"
32
+ "url": "git+https://github.com/prosopo/captcha.git",
33
+ "directory": "demos/client-example-server"
33
34
  },
34
35
  "author": "PROSOPO LIMITED <info@prosopo.io>",
35
36
  "license": "Apache-2.0",
@@ -41,12 +42,13 @@
41
42
  "@noble/hashes": "1.8.0",
42
43
  "@polkadot/util": "13.5.7",
43
44
  "@polkadot/util-crypto": "13.5.7",
44
- "@prosopo/common": "3.1.28",
45
- "@prosopo/dotenv": "3.0.33",
46
- "@prosopo/keyring": "2.9.0",
47
- "@prosopo/server": "2.9.52",
48
- "@prosopo/types": "3.8.0",
49
- "@prosopo/util": "3.2.5",
45
+ "@prosopo/common": "3.1.38",
46
+ "@prosopo/logger": "1.0.2",
47
+ "@prosopo/dotenv": "3.0.43",
48
+ "@prosopo/keyring": "2.9.34",
49
+ "@prosopo/server": "2.10.12",
50
+ "@prosopo/types": "4.3.0",
51
+ "@prosopo/util": "3.2.15",
50
52
  "@typegoose/auto-increment": "4.13.0",
51
53
  "cors": "2.8.5",
52
54
  "express": "4.21.2",
@@ -56,7 +58,7 @@
56
58
  "zod": "3.23.8"
57
59
  },
58
60
  "devDependencies": {
59
- "@prosopo/config": "3.3.0",
61
+ "@prosopo/config": "3.3.1",
60
62
  "@types/jsonwebtoken": "9.0.6",
61
63
  "@types/node": "22.10.2",
62
64
  "@vitest/coverage-v8": "3.2.4",
package/src/app.ts ADDED
@@ -0,0 +1,169 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import fs from "node:fs";
16
+ import http from "node:http";
17
+ import https from "node:https";
18
+ import type { ServerOptions } from "node:https";
19
+ import path from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { ProsopoEnvError } from "@prosopo/common";
22
+ import { loadEnv } from "@prosopo/dotenv";
23
+ import { getLogger } from "@prosopo/logger";
24
+ import { getServerConfig } from "@prosopo/server";
25
+ import { at } from "@prosopo/util";
26
+ import cors from "cors";
27
+ import express from "express";
28
+ import routesFactory from "./routes/routes.js";
29
+ import connectionFactory from "./utils/connection.js";
30
+ import memoryServerSetup from "./utils/database.js";
31
+
32
+ loadEnv();
33
+
34
+ enum ProsopoVerificationType {
35
+ api = "api",
36
+ local = "local",
37
+ }
38
+
39
+ const logger = getLogger("info", import.meta.url);
40
+
41
+ async function main() {
42
+ loadEnv();
43
+
44
+ const apiPrefix =
45
+ process.env.NODE_ENV && process.env.NODE_ENV === "production"
46
+ ? ""
47
+ : `${process.env.NODE_ENV}-`;
48
+ const verifyEndpoint =
49
+ process.env.PROSOPO_VERIFY_ENDPOINT ||
50
+ `https://${apiPrefix}api.prosopo.io/siteverify`;
51
+
52
+ logger.info(() => ({ data: { verifyEndpoint } }));
53
+
54
+ const verifyType: ProsopoVerificationType = Object.keys(
55
+ ProsopoVerificationType,
56
+ ).includes(process.env.PROSOPO_VERIFICATION_TYPE as string)
57
+ ? (process.env.PROSOPO_VERIFICATION_TYPE as ProsopoVerificationType)
58
+ : ProsopoVerificationType.api;
59
+
60
+ const app = express();
61
+
62
+ // https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues
63
+ app.set("trust proxy", 1);
64
+
65
+ app.use(cors({ origin: true, credentials: true }));
66
+
67
+ app.use(express.urlencoded({ extended: true }));
68
+
69
+ app.use(express.json());
70
+
71
+ app.use((_, res, next) => {
72
+ res.setHeader("Access-Control-Allow-Origin", "*");
73
+ res.setHeader(
74
+ "Access-Control-Allow-Methods",
75
+ "GET, POST, PUT, PATCH, DELETE",
76
+ );
77
+ res.setHeader(
78
+ "Access-Control-Allow-Headers",
79
+ "Origin, Content-Type, X-Auth-Token, Authorization",
80
+ );
81
+ next();
82
+ });
83
+
84
+ app.options("/*", (_, res) => {
85
+ res.sendStatus(200);
86
+ });
87
+
88
+ if (
89
+ !process.env.MONGO_URI &&
90
+ process.env.NODE_ENV !== "development" &&
91
+ process.env.NODE_ENV !== "test"
92
+ ) {
93
+ throw new Error(
94
+ "Cannot run mongo memory when NODE_ENV is neither development nor test",
95
+ );
96
+ }
97
+ logger.info(() => ({ msg: process.env.MONGO_URI }));
98
+ const uri = process.env.MONGO_URI || (await memoryServerSetup());
99
+ logger.info(() => ({ msg: "mongo uri", data: { uri } }));
100
+ const mongoose = connectionFactory(uri);
101
+ if (!process.env.PROSOPO_SITE_PRIVATE_KEY) {
102
+ const mnemonicError = new ProsopoEnvError("GENERAL.MNEMONIC_UNDEFINED", {
103
+ context: { missingParams: ["PROSOPO_SITE_PRIVATE_KEY"] },
104
+ logger,
105
+ });
106
+
107
+ logger.error(() => ({ err: mnemonicError }));
108
+ }
109
+
110
+ const config = getServerConfig();
111
+
112
+ logger.info(() => ({ msg: "Config", data: { config } }));
113
+
114
+ app.use(routesFactory(mongoose, config, verifyEndpoint, verifyType));
115
+
116
+ const port = config.serverUrl
117
+ ? Number.parseInt(at(config.serverUrl.split(":"), 2))
118
+ : 9228;
119
+
120
+ logger.info(() => ({ msg: "Listening on port", data: { port } }));
121
+
122
+ // Use HTTPS only in development/test when we have certificates
123
+ // In production, use HTTP because Caddy handles TLS termination
124
+ const isDev =
125
+ process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
126
+
127
+ if (isDev) {
128
+ const __filename = fileURLToPath(import.meta.url);
129
+ const __dirname = path.dirname(__filename);
130
+ const certsDir = path.resolve(__dirname, "../../../certs");
131
+
132
+ const keyPath = path.join(certsDir, "server.key");
133
+ const certPath = path.join(certsDir, "server.crt");
134
+
135
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
136
+ const httpsOptions: ServerOptions = {
137
+ key: fs.readFileSync(keyPath),
138
+ cert: fs.readFileSync(certPath),
139
+ };
140
+
141
+ https.createServer(httpsOptions, app).listen(port, () => {
142
+ logger.info(() => ({ msg: `HTTPS server started on port ${port}` }));
143
+ });
144
+ } else {
145
+ logger.warn(() => ({
146
+ msg: "Certificates not found, starting HTTP server instead. Run ./setup_certs.sh to enable HTTPS in development.",
147
+ }));
148
+ http.createServer(app).listen(port, () => {
149
+ logger.info(() => ({ msg: `HTTP server started on port ${port}` }));
150
+ });
151
+ }
152
+ } else {
153
+ // Production: use plain HTTP, Caddy handles TLS
154
+ http.createServer(app).listen(port, () => {
155
+ logger.info(() => ({
156
+ msg: `HTTP server started on port ${port} (TLS handled by reverse proxy)`,
157
+ }));
158
+ });
159
+ }
160
+ }
161
+
162
+ main()
163
+ .then(() => {
164
+ logger.info(() => ({ msg: "Server started" }));
165
+ })
166
+ .catch((err) => {
167
+ logger.error(() => ({ err }));
168
+ process.exit();
169
+ });
@@ -0,0 +1,371 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { blake2b } from "@noble/hashes/blake2b";
16
+ import { u8aToHex } from "@polkadot/util";
17
+ import { randomAsHex } from "@polkadot/util-crypto";
18
+ import { ProsopoEnvError } from "@prosopo/common";
19
+ import { getPair } from "@prosopo/keyring";
20
+ import { getLogger } from "@prosopo/logger";
21
+ import { ProsopoServer } from "@prosopo/server";
22
+ import {
23
+ ApiParams,
24
+ CaptchaType,
25
+ type KeyringPair,
26
+ ProcaptchaResponse,
27
+ type ProcaptchaToken,
28
+ type ProsopoServerConfigOutput,
29
+ } from "@prosopo/types";
30
+ import { at } from "@prosopo/util";
31
+ import type { NextFunction, Request, Response } from "express";
32
+ import jwt, { type JwtPayload } from "jsonwebtoken";
33
+ import type { Connection } from "mongoose";
34
+ import { z } from "zod";
35
+ import type { UserInterface } from "../models/user.js";
36
+
37
+ const logger = getLogger("info", import.meta.url);
38
+
39
+ const SubscribeBodySpec = ProcaptchaResponse.merge(
40
+ z.object({
41
+ email: z.string().email(),
42
+ password: z.string(),
43
+ siteKey: z.string(),
44
+ }),
45
+ );
46
+
47
+ function hashPassword(password: string): string {
48
+ return u8aToHex(blake2b(password));
49
+ }
50
+
51
+ const NO_IP = "NO_IP";
52
+
53
+ /**
54
+ * Derives the correct KeyringPair and secret for a given site key by trying different captcha types.
55
+ * @param baseSecret - The base secret from config
56
+ * @param siteKey - The site key to match against
57
+ * @returns Object containing the matched pair and its corresponding secret
58
+ * @throws Error if no matching pair is found
59
+ */
60
+ const getPairAndSecretForSiteKey = (
61
+ baseSecret: string,
62
+ siteKey: string,
63
+ ): { pair: KeyringPair; secret: string } => {
64
+ let pair: KeyringPair | undefined;
65
+ let secret = baseSecret;
66
+
67
+ pair = getPair(secret);
68
+
69
+ if (pair.address === siteKey) {
70
+ logger.info(() => ({
71
+ msg: "Site key matches the server configuration without captcha type suffix",
72
+ data: {
73
+ siteKeyDerived: pair?.address,
74
+ siteKey,
75
+ },
76
+ }));
77
+ return { pair, secret };
78
+ }
79
+
80
+ for (const captchaType of [
81
+ CaptchaType.pow,
82
+ CaptchaType.image,
83
+ CaptchaType.frictionless,
84
+ CaptchaType.puzzle,
85
+ ]) {
86
+ const newSecret = `${baseSecret}//${captchaType}`;
87
+ pair = getPair(newSecret);
88
+ console.log(
89
+ `[PAIR CHECK] Checking pair for ${captchaType}: ${pair?.address} (expected: ${siteKey})`,
90
+ );
91
+ logger.info(() => ({
92
+ msg: "Checking pair",
93
+ data: {
94
+ captchaType,
95
+ siteKeyDerived: pair?.address,
96
+ siteKey,
97
+ },
98
+ }));
99
+ if (pair.address === siteKey) {
100
+ console.log(`[PAIR CHECK] ✅ Match found for ${captchaType}!`);
101
+ secret = newSecret;
102
+ break;
103
+ }
104
+ }
105
+
106
+ if (!pair || pair.address !== siteKey) {
107
+ throw new Error(
108
+ `Site key does not match the server configuration: Provided ${siteKey} vs Server ${pair?.address}`,
109
+ );
110
+ }
111
+
112
+ return { pair, secret };
113
+ };
114
+
115
+ const getResponse = async (
116
+ ip: string,
117
+ token: ProcaptchaToken,
118
+ secret: string,
119
+ verifyEndpoint: string,
120
+ email?: string,
121
+ ) => {
122
+ // Only include ip if environment is production
123
+ const body: Record<string, string> = {
124
+ [ApiParams.token]: token,
125
+ [ApiParams.secret]: secret,
126
+ ...(email ? { [ApiParams.email]: email } : {}),
127
+ };
128
+
129
+ if (process.env.NODE_ENV !== "development") {
130
+ body[ApiParams.ip] = ip;
131
+ }
132
+
133
+ logger.info(() => ({
134
+ data: {
135
+ body,
136
+ },
137
+ }));
138
+
139
+ const response = await fetch(verifyEndpoint, {
140
+ method: "POST",
141
+ body: JSON.stringify(body),
142
+ });
143
+ return response.json();
144
+ };
145
+
146
+ const verify = async (
147
+ prosopoServer: ProsopoServer,
148
+ verifyType: string,
149
+ verifyEndpoint: string,
150
+ token: ProcaptchaToken,
151
+ secret: string,
152
+ ip: string,
153
+ email?: string,
154
+ ) => {
155
+ if (verifyType === "api") {
156
+ // verify using the API endpoint
157
+ logger.info(() => ({
158
+ msg: "Verifying using the API endpoint",
159
+ data: {
160
+ verifyEndpoint,
161
+ },
162
+ }));
163
+
164
+ const response = await getResponse(
165
+ ip,
166
+ token,
167
+ secret,
168
+ verifyEndpoint,
169
+ email,
170
+ );
171
+ logger.info(() => ({
172
+ data: {
173
+ response,
174
+ },
175
+ }));
176
+ return response.verified;
177
+ }
178
+ // verify using the TypeScript library
179
+ const verified = await prosopoServer.isVerified(token, ip, email);
180
+ logger.info(() => ({
181
+ data: {
182
+ verified,
183
+ },
184
+ }));
185
+ return verified[ApiParams.verified];
186
+ };
187
+
188
+ const signup = async (
189
+ mongoose: Connection,
190
+ config: ProsopoServerConfigOutput,
191
+ verifyEndpoint: string,
192
+ verifyType: string,
193
+ req: Request,
194
+ res: Response,
195
+ next: NextFunction,
196
+ ) => {
197
+ try {
198
+ if (!config.account.secret) {
199
+ throw new ProsopoEnvError("GENERAL.MNEMONIC_UNDEFINED", {
200
+ context: { missingParams: ["PROSOPO_SITE_PRIVATE_KEY"] },
201
+ });
202
+ }
203
+ const User = mongoose.model<UserInterface>("User");
204
+ // checks if email exists
205
+ const dbUser = await User.findOne({
206
+ email: { $eq: req.body.email },
207
+ });
208
+ const payload = SubscribeBodySpec.parse(req.body);
209
+ // get the procaptcha-response token
210
+ const token = payload[ApiParams.procaptchaResponse];
211
+ const siteKey = payload[ApiParams.siteKey];
212
+
213
+ const { pair, secret } = getPairAndSecretForSiteKey(
214
+ config.account.secret,
215
+ siteKey,
216
+ );
217
+
218
+ const prosopoServer = new ProsopoServer(config, pair);
219
+ if (dbUser) {
220
+ return res.status(409).json({ message: "email already exists" });
221
+ }
222
+
223
+ const email = req.body.email;
224
+ const verified = await verify(
225
+ prosopoServer,
226
+ verifyType,
227
+ verifyEndpoint,
228
+ token,
229
+ secret,
230
+ req.headers["x-client-ip"]?.toString() || "127.0.0.1",
231
+ email,
232
+ );
233
+
234
+ if (verified === true) {
235
+ // salt
236
+ const salt = randomAsHex(32);
237
+ // !!!DUMMY CODE!!! - Do not use in production. Use bcrypt or similar for password hashing.
238
+ const passwordHash = hashPassword(`${req.body.password}${salt}`);
239
+ if (passwordHash) {
240
+ return User.create({
241
+ email: email,
242
+ name: req.body.name,
243
+ password: passwordHash,
244
+ salt: salt,
245
+ })
246
+ .then(() => {
247
+ res.status(200).json({ message: "user created" });
248
+ })
249
+ .catch((err) => {
250
+ logger.error(() => ({
251
+ err,
252
+ msg: "Error creating user in database",
253
+ }));
254
+ res.status(502).json({ message: "error while creating the user" });
255
+ });
256
+ }
257
+ } else {
258
+ res
259
+ .status(401)
260
+ .json({ message: "user has not completed a captcha", verified });
261
+ }
262
+ } catch (err) {
263
+ console.error("error", err);
264
+ res
265
+ .status(500)
266
+ .json({ message: (err as Error).message || "internal server error" });
267
+ }
268
+ };
269
+
270
+ const login = async (
271
+ mongoose: Connection,
272
+ config: ProsopoServerConfigOutput,
273
+ verifyEndpoint: string,
274
+ verifyType: string,
275
+ req: Request,
276
+ res: Response,
277
+ ) => {
278
+ const User = mongoose.model<UserInterface>("User");
279
+
280
+ // checks if email exists
281
+ await User.findOne({
282
+ email: { $eq: req.body.email },
283
+ })
284
+ .then(async (dbUser) => {
285
+ if (dbUser) {
286
+ const payload = SubscribeBodySpec.parse(req.body);
287
+
288
+ const token = payload[ApiParams.procaptchaResponse];
289
+ const siteKey = payload[ApiParams.siteKey];
290
+
291
+ if (!config.account.secret) {
292
+ throw new ProsopoEnvError("GENERAL.SECRET_MISSING", {
293
+ context: { missingParams: ["PROSOPO_SITE_PRIVATE_KEY"] },
294
+ });
295
+ }
296
+
297
+ const { pair, secret } = getPairAndSecretForSiteKey(
298
+ config.account.secret,
299
+ siteKey,
300
+ );
301
+ const prosopoServer = new ProsopoServer(config, pair);
302
+
303
+ const verified = await verify(
304
+ prosopoServer,
305
+ verifyType,
306
+ verifyEndpoint,
307
+ token,
308
+ secret,
309
+ req.headers["x-client-ip"]?.toString() || NO_IP,
310
+ );
311
+
312
+ logger.info(() => ({
313
+ data: {
314
+ verified,
315
+ },
316
+ }));
317
+
318
+ if (verified) {
319
+ // password hash
320
+ // !!!DUMMY CODE!!! - Do not use in production. Use bcrypt or similar for password hashing.
321
+ const passwordHash = hashPassword(
322
+ `${req.body.password}${dbUser.salt}`,
323
+ );
324
+ if (passwordHash !== dbUser.password) {
325
+ // password doesnt match
326
+ return res.status(401).json({ message: "invalid credentials" });
327
+ }
328
+ // password match
329
+ const token = jwt.sign({ email: req.body.email }, "secret", {
330
+ expiresIn: "1h",
331
+ });
332
+ return res
333
+ .status(200)
334
+ .json({ message: "user logged in", token: token });
335
+ }
336
+ return res
337
+ .status(401)
338
+ .json({ message: "user has not completed a captcha", verified });
339
+ }
340
+ return res.status(404).json({ message: "user not found" });
341
+ })
342
+ .catch((err) => {
343
+ console.error("error", err);
344
+ res.status(500).json({ message: err.message || "internal server error" });
345
+ });
346
+ };
347
+
348
+ const isAuth = (req: Request, res: Response) => {
349
+ const authHeader = req.get("Authorization") || "";
350
+ if (!authHeader) {
351
+ res.status(401).json({ message: "not authenticated" });
352
+ }
353
+
354
+ const token = at(authHeader.split(" "), 1);
355
+ let decodedToken: string | JwtPayload = "";
356
+ try {
357
+ decodedToken = jwt.verify(token, "secret");
358
+ } catch (err) {
359
+ res.status(500).json({
360
+ message: (err as Error).message || "could not decode the token",
361
+ });
362
+ }
363
+
364
+ if (!decodedToken) {
365
+ res.status(401).json({ message: "unauthorized" });
366
+ } else {
367
+ res.status(200).json({ message: "here is your resource" });
368
+ }
369
+ };
370
+
371
+ export { signup, login, isAuth };
@@ -0,0 +1,45 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import mongoose, { Schema } from "mongoose";
15
+
16
+ export interface UserInterface {
17
+ id: number;
18
+ email: string;
19
+ name: string;
20
+ password: string;
21
+ salt: string;
22
+ }
23
+
24
+ const UserSchema = new Schema<UserInterface>({
25
+ email: {
26
+ type: mongoose.SchemaTypes.String,
27
+ required: true,
28
+ },
29
+ name: {
30
+ type: mongoose.SchemaTypes.String,
31
+ },
32
+ password: {
33
+ type: mongoose.SchemaTypes.String,
34
+ required: true,
35
+ },
36
+ id: {
37
+ type: mongoose.SchemaTypes.Number,
38
+ },
39
+ salt: {
40
+ type: mongoose.SchemaTypes.String,
41
+ required: true,
42
+ },
43
+ });
44
+
45
+ export default UserSchema;
@@ -0,0 +1,54 @@
1
+ import type { ProsopoServerConfigOutput } from "@prosopo/types";
2
+ import express from "express";
3
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ import type { Connection } from "mongoose";
17
+ import { isAuth, login, signup } from "../controllers/auth.js";
18
+
19
+ const router = express.Router();
20
+
21
+ function getRoutes(
22
+ mongoose: Connection,
23
+ config: ProsopoServerConfigOutput,
24
+ verifyEndpoint: string,
25
+ verifyType: string,
26
+ ): express.Router {
27
+ router.post(
28
+ "/login",
29
+ login.bind(null, mongoose, config, verifyEndpoint, verifyType),
30
+ );
31
+
32
+ router.post(
33
+ "/signup",
34
+ signup.bind(null, mongoose, config, verifyEndpoint, verifyType),
35
+ );
36
+
37
+ router.get("/private", isAuth);
38
+
39
+ router.get("/public", (req, res) => {
40
+ res.status(200).json({ message: "here is your public resource" });
41
+ });
42
+
43
+ router.get("/health", (req, res) => {
44
+ res.status(200).json({ status: "ok" });
45
+ });
46
+
47
+ // will match any other path
48
+ router.use("/", (req, res) => {
49
+ res.status(404).json({ error: "page not found" });
50
+ });
51
+ return router;
52
+ }
53
+
54
+ export default getRoutes;