@xenterprises/fastify-xauth-jwks 1.0.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.
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Generate demo JWT tokens for testing xAuthJWSK
5
+ *
6
+ * Usage:
7
+ * node generate-demo-token.js [pathName] [userId] [role]
8
+ *
9
+ * Examples:
10
+ * node generate-demo-token.js admin user-123 admin
11
+ * node generate-demo-token.js portal user-456 user
12
+ * node generate-demo-token.js partner partner-789
13
+ */
14
+
15
+ import * as jose from 'jose';
16
+
17
+ // Demo JWKS keys - these are public keys only
18
+ // In production, private keys would be stored securely
19
+ const demoKeys = {
20
+ admin: {
21
+ kid: 'demo-key-admin',
22
+ // This is a demo key for testing ONLY - never use in production
23
+ privateKeyPem: `-----BEGIN PRIVATE KEY-----
24
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGOUJEGqRBZ4Gl
25
+ oxZXYTaHovXNyqbGbRbdnkFszA/2N9gQn9ke8x1Xx9hpYNXzHMaovtn19BovttmZ
26
+ XNhw/PRerkv9WYTxXsrQnUjczUjczVTNzNT0xM01M1MzQzM1M9T1M9M1TzU0MzU0
27
+ M1MzQzMzQ0M1MzMzM1M1QzMzMzM1RDMzM1M1QzMzM1M1QzMzM1M1M1MzMzM1M1Mz
28
+ MzM1M1MzMzM1M1MzMzMzUzMzMzM1M1MzMzMzUzMzMzM1M1MzMzMzUzMzMzM1M1Mz
29
+ MzMzUzMzMzM1M1MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
30
+ MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
31
+ MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
32
+ AgMBAAECggEAQQECDQwVFBcWGRgbHBwdHR4eHx8gICEhISIiIiIjIyMjIyMkJCQk
33
+ JCQlJSUlJSUlJSYmJiYmJiYmJiYnJycnJycnJycnJycnJycnJycnJycnJycnJycnJ
34
+ yckJycnJycnJycoJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyc
35
+ nJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
36
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
37
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
38
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
39
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
40
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
41
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
42
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
43
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
44
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
45
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
46
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
47
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
48
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
49
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
50
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
51
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
52
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
53
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
54
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
55
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
56
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
57
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
58
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
59
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
60
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
61
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
62
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
63
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
64
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
65
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
66
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
67
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
68
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
69
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
70
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
71
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
72
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
73
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
74
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
75
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
76
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
77
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
78
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
79
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
80
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
81
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
82
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
83
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
84
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
85
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
86
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
87
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
88
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
89
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
90
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
91
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
92
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
93
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
94
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
95
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
96
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
97
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
98
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
99
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
100
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
101
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
102
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
103
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
104
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
105
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
106
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
107
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
108
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
109
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
110
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
111
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
112
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
113
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
114
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
115
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
116
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
117
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
118
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
119
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
120
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
121
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
122
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
123
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
124
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
125
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
126
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
127
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
128
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
129
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
130
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
131
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
132
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
133
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
134
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
135
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
136
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
137
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
138
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
139
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
140
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
141
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
142
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
143
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
144
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
145
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
146
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
147
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
148
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
149
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
150
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
151
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
152
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
153
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
154
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
155
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
156
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
157
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
158
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
159
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
160
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
161
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
162
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
163
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
164
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
165
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
166
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
167
+ cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
168
+ -----END PRIVATE KEY-----`,
169
+ },
170
+ };
171
+
172
+ async function generateToken() {
173
+ const args = process.argv.slice(2);
174
+ const pathName = args[0] || 'admin';
175
+ const userId = args[1] || 'user-' + Math.random().toString(36).substring(7);
176
+ const role = args[2] || 'user';
177
+
178
+ const keyConfig = demoKeys[pathName];
179
+ if (!keyConfig) {
180
+ console.error(`❌ No demo key for path: ${pathName}`);
181
+ console.error(` Available paths: ${Object.keys(demoKeys).join(', ')}`);
182
+ process.exit(1);
183
+ }
184
+
185
+ try {
186
+ // Import private key
187
+ const privateKey = await jose.importPKCS8(
188
+ keyConfig.privateKeyPem,
189
+ 'RS256'
190
+ );
191
+
192
+ // Create JWT
193
+ const token = await new jose.SignJWT({
194
+ sub: userId,
195
+ userId: userId,
196
+ name: `Test User (${pathName})`,
197
+ email: `${userId}@example.com`,
198
+ roles: [role],
199
+ iat: Math.floor(Date.now() / 1000),
200
+ exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
201
+ })
202
+ .setProtectedHeader({
203
+ alg: 'RS256',
204
+ kid: `demo-key-${pathName}`,
205
+ typ: 'JWT',
206
+ })
207
+ .sign(privateKey);
208
+
209
+ console.log('🎫 Generated Test Token:\n');
210
+ console.log(token);
211
+ console.log('\n📋 Token Details:');
212
+ console.log(` Path: ${pathName}`);
213
+ console.log(` User ID: ${userId}`);
214
+ console.log(` Role: ${role}`);
215
+ console.log(` Expires: 1 hour`);
216
+ console.log('\n🔗 Usage Examples:\n');
217
+ console.log('# cURL:');
218
+ console.log(`curl -H "Authorization: Bearer ${token}" \\`);
219
+ console.log(` http://localhost:3000/${pathName}/dashboard\n`);
220
+ console.log('# JavaScript fetch:');
221
+ console.log(`fetch('http://localhost:3000/${pathName}/dashboard', {`);
222
+ console.log(` headers: { Authorization: 'Bearer ${token}' }`);
223
+ console.log(`})\n`);
224
+ console.log('# Decode token (for inspection):');
225
+ console.log(`node -e "console.log(JSON.stringify(JSON.parse(Buffer.from('${token}'.split('.')[1], 'base64').toString()), null, 2))"\n`);
226
+ } catch (error) {
227
+ console.error('❌ Error generating token:', error.message);
228
+ process.exit(1);
229
+ }
230
+ }
231
+
232
+ generateToken();
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // src/index.js
2
+
3
+ /**
4
+ * Main exports for xAuth plugin
5
+ */
6
+
7
+ export { default } from "./xAuth.js";
8
+ export { default as xAuth } from "./xAuth.js";
9
+ export * as utils from "./utils/index.js";
@@ -0,0 +1,175 @@
1
+ // src/services/pathValidator.js
2
+ import * as jose from "jose";
3
+ import { extractToken } from "../utils/index.js";
4
+
5
+ export async function createPathValidator(fastify, pathName, config) {
6
+ const {
7
+ pathPattern = `/${pathName}`,
8
+ jwksUrl,
9
+ jwksData,
10
+ excludedPaths = [],
11
+ // JWKS caching configuration
12
+ jwksCooldownDuration = 30000, // 30 seconds between refetches
13
+ jwksCacheMaxAge = 1800000, // 30 minutes cache duration
14
+ // JWT payload caching configuration
15
+ enablePayloadCache = true,
16
+ payloadCacheTTL = 300000, // 5 minutes default
17
+ } = config;
18
+
19
+ // Validate configuration
20
+ if (!jwksUrl && !jwksData) {
21
+ throw new Error(`${pathName}: Either jwksUrl or jwksData is required`);
22
+ }
23
+
24
+ if (jwksUrl && jwksData) {
25
+ throw new Error(`${pathName}: Cannot specify both jwksUrl and jwksData`);
26
+ }
27
+
28
+ // Create JWKS for token verification
29
+ let jwks;
30
+ if (jwksUrl) {
31
+ // Remote JWKS endpoint
32
+ jwks = jose.createRemoteJWKSet(new URL(jwksUrl), {
33
+ cooldownDuration: jwksCooldownDuration,
34
+ cacheMaxAge: jwksCacheMaxAge,
35
+ });
36
+ } else {
37
+ // Local JWKS data - can be either:
38
+ // 1. Full JWKS object: { keys: [...] }
39
+ // 2. Single JWK key: { kty: "RSA", ... } (will be wrapped in JWKS format)
40
+ const jwksFormatted = jwksData.keys ? jwksData : { keys: [jwksData] };
41
+ jwks = jose.createLocalJWKSet(jwksFormatted);
42
+ }
43
+
44
+ // JWT payload cache (token -> {payload, expiresAt})
45
+ const payloadCache = new Map();
46
+
47
+ // Build full excluded paths
48
+ const fullExcludedPaths = excludedPaths.map((path) => {
49
+ if (path.startsWith("/")) {
50
+ return `${pathPattern}${path}`;
51
+ }
52
+ return `${pathPattern}/${path}`;
53
+ });
54
+
55
+ // JWT verification function with optional payload caching
56
+ async function verifyJWT(accessToken) {
57
+ try {
58
+ if (!accessToken || typeof accessToken !== "string") {
59
+ fastify.log.error(`[${pathName}] Invalid token format`);
60
+ return null;
61
+ }
62
+
63
+ // Check payload cache first if enabled
64
+ if (enablePayloadCache) {
65
+ const cached = payloadCache.get(accessToken);
66
+ if (cached && cached.expiresAt > Date.now()) {
67
+ return cached.payload;
68
+ } else if (cached) {
69
+ // Expired, remove from cache
70
+ payloadCache.delete(accessToken);
71
+ }
72
+ }
73
+
74
+ const { payload } = await jose.jwtVerify(accessToken, jwks);
75
+
76
+ if (!payload || !payload.sub) {
77
+ fastify.log.error(`[${pathName}] Invalid token payload`);
78
+ return null;
79
+ }
80
+
81
+ // Cache the payload if enabled
82
+ if (enablePayloadCache) {
83
+ payloadCache.set(accessToken, {
84
+ payload,
85
+ expiresAt: Date.now() + payloadCacheTTL,
86
+ });
87
+ }
88
+
89
+ return payload;
90
+ } catch (error) {
91
+ fastify.log.error(`[${pathName}] Token verification failed:`, error.message);
92
+ return null;
93
+ }
94
+ }
95
+
96
+ // Cache cleanup function (optional, for manual cache management)
97
+ function clearPayloadCache() {
98
+ payloadCache.clear();
99
+ }
100
+
101
+ // Get cache stats (for monitoring)
102
+ function getPayloadCacheStats() {
103
+ return {
104
+ size: payloadCache.size,
105
+ enabled: enablePayloadCache,
106
+ ttl: payloadCacheTTL,
107
+ };
108
+ }
109
+
110
+ // Authentication middleware
111
+ fastify.addHook("onRequest", async (request, reply) => {
112
+ const url = request.url;
113
+
114
+ // Only protect paths matching this pattern
115
+ if (!url.startsWith(pathPattern)) {
116
+ return;
117
+ }
118
+
119
+ // Skip excluded paths
120
+ if (fullExcludedPaths.some((path) => url.startsWith(path))) {
121
+ return;
122
+ }
123
+
124
+ try {
125
+ // Get token from header
126
+ const token = extractToken(request);
127
+ if (!token) {
128
+ return reply.code(401).send({
129
+ error: "Access token required",
130
+ path: pathName,
131
+ });
132
+ }
133
+
134
+ // Verify token
135
+ const payload = await verifyJWT(token);
136
+
137
+ if (!payload) {
138
+ return reply.code(401).send({
139
+ error: "Invalid token",
140
+ path: pathName,
141
+ });
142
+ }
143
+
144
+ // Set user info on request
145
+ request.user = payload;
146
+ request.auth = {
147
+ path: pathName,
148
+ userId: payload.sub,
149
+ payload,
150
+ };
151
+ } catch (err) {
152
+ fastify.log.error(`[${pathName}] Authentication failed:`, err);
153
+ return reply.code(401).send({
154
+ error: "Authentication failed",
155
+ path: pathName,
156
+ });
157
+ }
158
+ });
159
+
160
+ // Return validator info and methods
161
+ return {
162
+ name: pathName,
163
+ pathPattern,
164
+ jwksUrl,
165
+ verifyJWT,
166
+ clearPayloadCache,
167
+ getPayloadCacheStats,
168
+ config: {
169
+ jwksCooldownDuration,
170
+ jwksCacheMaxAge,
171
+ enablePayloadCache,
172
+ payloadCacheTTL,
173
+ },
174
+ };
175
+ }
@@ -0,0 +1,145 @@
1
+ // src/utils/index.js
2
+ import { decodeJwt, decodeProtectedHeader } from "jose";
3
+
4
+ /**
5
+ * Extract JWT token from Authorization header
6
+ * @param {object} request - Fastify request object
7
+ * @returns {string|null} - JWT token or null
8
+ */
9
+ export function extractToken(request) {
10
+ const authHeader = request.headers.authorization;
11
+
12
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
13
+ return null;
14
+ }
15
+
16
+ return authHeader.slice(7);
17
+ }
18
+
19
+ /**
20
+ * Check if user has specific role
21
+ * @param {object} user - User payload from JWT
22
+ * @param {string|Array<string>} roles - Role(s) to check
23
+ * @returns {boolean}
24
+ */
25
+ export function hasRole(user, roles) {
26
+ if (!user || !user.roles) {
27
+ return false;
28
+ }
29
+
30
+ const userRoles = Array.isArray(user.roles) ? user.roles : [user.roles];
31
+ const requiredRoles = Array.isArray(roles) ? roles : [roles];
32
+
33
+ return requiredRoles.some((role) => userRoles.includes(role));
34
+ }
35
+
36
+ /**
37
+ * Check if user has specific permission
38
+ * @param {object} user - User payload from JWT
39
+ * @param {string|Array<string>} permissions - Permission(s) to check
40
+ * @returns {boolean}
41
+ */
42
+ export function hasPermission(user, permissions) {
43
+ if (!user || !user.permissions) {
44
+ return false;
45
+ }
46
+
47
+ const userPermissions = Array.isArray(user.permissions)
48
+ ? user.permissions
49
+ : [user.permissions];
50
+ const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
51
+
52
+ return requiredPermissions.some((permission) => userPermissions.includes(permission));
53
+ }
54
+
55
+ /**
56
+ * Get user ID from request
57
+ * @param {object} request - Fastify request object
58
+ * @returns {string|null}
59
+ */
60
+ export function getUserId(request) {
61
+ return request.auth?.userId || request.user?.sub || null;
62
+ }
63
+
64
+ /**
65
+ * Get auth endpoint name from request
66
+ * @param {object} request - Fastify request object
67
+ * @returns {string|null}
68
+ */
69
+ export function getAuthEndpoint(request) {
70
+ return request.auth?.endpoint || null;
71
+ }
72
+
73
+ /**
74
+ * Create role check decorator for Fastify routes
75
+ * @param {string|Array<string>} roles - Required role(s)
76
+ * @returns {Function} - Fastify preHandler hook
77
+ */
78
+ export function requireRole(roles) {
79
+ return async function (request, reply) {
80
+ if (!hasRole(request.user, roles)) {
81
+ return reply.code(403).send({
82
+ error: "Forbidden",
83
+ message: "Insufficient permissions",
84
+ });
85
+ }
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Create permission check decorator for Fastify routes
91
+ * @param {string|Array<string>} permissions - Required permission(s)
92
+ * @returns {Function} - Fastify preHandler hook
93
+ */
94
+ export function requirePermission(permissions) {
95
+ return async function (request, reply) {
96
+ if (!hasPermission(request.user, permissions)) {
97
+ return reply.code(403).send({
98
+ error: "Forbidden",
99
+ message: "Insufficient permissions",
100
+ });
101
+ }
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Create endpoint check decorator for Fastify routes
107
+ * @param {string} endpointName - Required endpoint name
108
+ * @returns {Function} - Fastify preHandler hook
109
+ */
110
+ export function requireEndpoint(endpointName) {
111
+ return async function (request, reply) {
112
+ if (getAuthEndpoint(request) !== endpointName) {
113
+ return reply.code(403).send({
114
+ error: "Forbidden",
115
+ message: `Must authenticate via ${endpointName} endpoint`,
116
+ });
117
+ }
118
+ };
119
+ }
120
+
121
+ // ============================================================================
122
+ // Re-exported jose utilities for advanced use cases
123
+ // ============================================================================
124
+
125
+ /**
126
+ * Decode JWT payload without verification
127
+ * Useful for inspecting token claims before processing
128
+ * @param {string} token - JWT token
129
+ * @returns {object} - Decoded payload
130
+ * @example
131
+ * const payload = decodeToken('eyJ...');
132
+ * console.log(payload.sub, payload.exp);
133
+ */
134
+ export { decodeJwt as decodeToken };
135
+
136
+ /**
137
+ * Decode JWT header without verification
138
+ * Useful for inspecting algorithm and key ID
139
+ * @param {string} token - JWT token
140
+ * @returns {object} - Decoded header
141
+ * @example
142
+ * const header = decodeHeader('eyJ...');
143
+ * console.log(header.alg, header.kid);
144
+ */
145
+ export { decodeProtectedHeader as decodeHeader };
package/src/xAuth.js ADDED
@@ -0,0 +1,36 @@
1
+ // src/xAuth.js
2
+ import fp from "fastify-plugin";
3
+ import { createPathValidator } from "./services/pathValidator.js";
4
+
5
+ async function xAuthPlugin(fastify, options) {
6
+ const { paths = {} } = options;
7
+
8
+ if (Object.keys(paths).length === 0) {
9
+ throw new Error("At least one protected path configuration is required");
10
+ }
11
+
12
+ // Create xAuth namespace
13
+ const xAuth = {
14
+ validators: {},
15
+ };
16
+
17
+ // Setup validator for each path
18
+ for (const [pathName, pathConfig] of Object.entries(paths)) {
19
+ if (pathConfig.active === false) {
20
+ continue;
21
+ }
22
+
23
+ // Create path validator with JWKS
24
+ const validator = await createPathValidator(fastify, pathName, pathConfig);
25
+
26
+ // Store validator in xAuth namespace
27
+ xAuth.validators[pathName] = validator;
28
+ }
29
+
30
+ // Decorate Fastify with xAuth namespace
31
+ fastify.decorate("xAuth", xAuth);
32
+ }
33
+
34
+ export default fp(xAuthPlugin, {
35
+ name: "xAuth",
36
+ });