-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathapp.js
More file actions
156 lines (141 loc) · 4.42 KB
/
Copy pathapp.js
File metadata and controls
156 lines (141 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import Fastify from "fastify";
import authRoutes from "./routes/auth.routes.js";
import adminRoutes from "./routes/admin.routes.js";
import database from "./db/db.js";
import serverKey from "./key/server.key.js";
import adminKey from "./key/admin.key.js";
import fastifyStatic from "@fastify/static";
import fastifySwagger from "@fastify/swagger";
import fastifySwaggerUi from "@fastify/swagger-ui";
import { readFileSync } from "fs";
import path from "path";
// Constants for API versioning and route prefixes
const API_VERSION = "v1";
const ROUTE_PREFIXES = {
ADMIN: `/${API_VERSION}/admin`,
AUTH: `/${API_VERSION}/auth`,
};
export const buildApp = async (serverOptions = {}) => {
const app = Fastify(serverOptions);
// Cache the SPA page at server start (not on every request)
const spaPath = path.join(process.cwd(), "client", "dist", "index.html");
let spaPage;
try {
spaPage = readFileSync(spaPath, "utf8");
} catch (error) {
throw new Error(`Failed to cache SPA page: ${error.message}`);
}
// Register core plugins first
await app.register(database).register(serverKey).register(adminKey);
// Register static assets plugin
await app.register(fastifyStatic, {
root: path.join(process.cwd(), "client", "dist"),
prefix: "/", // Serve from root path
decorateReply: false, // Disable automatic headers
});
// Register OpenAPI Documentation
await app.register(fastifySwagger, {
openapi: {
info: {
title: "Server API Documentation",
description: `Comprehensive API documentation for AuthCompanion's secure authentication Server.
Features
- User authentication & session management
- Admin-level user administration
- Passwordless WebAuthn integration
Try endpoints directly from this documentation for interactive testing.`,
version: "5.0.0",
},
servers: [
{
url: "http://localhost:3002",
description: "Local development server",
},
],
tags: [
{
name: "Auth API",
description:
"Authentication flows including login, registration, and token management; that powers the public web forms.",
},
{
name: "Admin API",
description: "Privileged user management operations requiring admin permissions",
},
{
name: "WebAuthn API",
description: "FIDO2 WebAuthn integration for passwordless authentication flows",
},
{
name: "Health Checks",
description: "Real-time server status monitoring and service health verification",
},
],
components: {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "Admin JWT token obtained through the admin login endpoint",
},
},
},
},
});
await app.register(fastifySwaggerUi, {
routePrefix: "/docs/api",
uiConfig: {
docExpansion: "list",
deepLinking: false,
},
});
// Register routes with proper error handling
const plugins = [
{ plugin: adminRoutes, options: { prefix: ROUTE_PREFIXES.ADMIN } },
{ plugin: authRoutes, options: { prefix: ROUTE_PREFIXES.AUTH } },
];
for (const { plugin, options } of plugins) {
await app.register(plugin, options);
}
// Add health check endpoints
app.register(async (fastify) => {
fastify.get(
"/health",
{
schema: {
description: "For health checking.",
tags: ["Health Checks"],
summary: "Check the health of the AuthC Server v5",
},
},
async () => ({ status: "OK" })
);
});
// Add SPA catch-all route
app.setNotFoundHandler((request, reply) => {
reply.code(200).header("Content-Type", "text/html; charset=utf-8").send(spaPage);
});
// Add global error handler
app.setErrorHandler((error, request, reply) => {
app.log.error(error);
reply.status(error.statusCode || 500).send({
error: {
message: error.message,
code: error.code,
},
});
});
// Add graceful shutdown
app.addHook("onClose", async (instance) => {
await instance.database?.disconnect?.();
});
try {
await app.ready();
return app;
} catch (error) {
app.log.error(error);
await app.close();
throw new Error("Failed to initialize application");
}
};