nitrostack 1.0.0 → 1.0.1
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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/templates/typescript-auth/.env.example +23 -0
- package/templates/typescript-auth/src/app.module.ts +103 -0
- package/templates/typescript-auth/src/db/database.ts +163 -0
- package/templates/typescript-auth/src/db/seed.ts +374 -0
- package/templates/typescript-auth/src/db/setup.ts +87 -0
- package/templates/typescript-auth/src/events/analytics.service.ts +52 -0
- package/templates/typescript-auth/src/events/notification.service.ts +40 -0
- package/templates/typescript-auth/src/filters/global-exception.filter.ts +28 -0
- package/templates/typescript-auth/src/guards/README.md +75 -0
- package/templates/typescript-auth/src/guards/jwt.guard.ts +105 -0
- package/templates/typescript-auth/src/health/database.health.ts +41 -0
- package/templates/typescript-auth/src/index.ts +26 -0
- package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +24 -0
- package/templates/typescript-auth/src/middleware/logging.middleware.ts +42 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +16 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +114 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +40 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +241 -0
- package/templates/typescript-auth/src/modules/auth/auth.module.ts +16 -0
- package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +147 -0
- package/templates/typescript-auth/src/modules/auth/auth.resources.ts +84 -0
- package/templates/typescript-auth/src/modules/auth/auth.tools.ts +139 -0
- package/templates/typescript-auth/src/modules/cart/cart.module.ts +16 -0
- package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +95 -0
- package/templates/typescript-auth/src/modules/cart/cart.resources.ts +44 -0
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/orders/orders.module.ts +16 -0
- package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +88 -0
- package/templates/typescript-auth/src/modules/orders/orders.resources.ts +48 -0
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/products/products.module.ts +16 -0
- package/templates/typescript-auth/src/modules/products/products.prompts.ts +146 -0
- package/templates/typescript-auth/src/modules/products/products.resources.ts +98 -0
- package/templates/typescript-auth/src/modules/products/products.tools.ts +266 -0
- package/templates/typescript-auth/src/pipes/validation.pipe.ts +42 -0
- package/templates/typescript-auth/src/services/database.service.ts +90 -0
- package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +122 -0
- package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +105 -0
- package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +139 -0
- package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +153 -0
- package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +86 -0
- package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/categories/page.tsx +134 -0
- package/templates/typescript-auth/src/widgets/app/layout.tsx +21 -0
- package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +129 -0
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +206 -0
- package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +225 -0
- package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +218 -0
- package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +121 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +173 -0
- package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +187 -0
- package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +165 -0
- package/templates/typescript-auth/src/widgets/next.config.js +38 -0
- package/templates/typescript-auth/src/widgets/package.json +18 -0
- package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +169 -0
- package/templates/typescript-auth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth/src/widgets/types/tool-data.ts +141 -0
- package/templates/typescript-auth/src/widgets/widget-manifest.json +464 -0
- package/templates/typescript-auth/tsconfig.json +27 -0
- package/templates/typescript-auth-api-key/.env +15 -0
- package/templates/typescript-auth-api-key/.env.example +4 -0
- package/templates/typescript-auth-api-key/src/app.module.ts +38 -0
- package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +47 -0
- package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +157 -0
- package/templates/typescript-auth-api-key/src/health/system.health.ts +55 -0
- package/templates/typescript-auth-api-key/src/index.ts +47 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +60 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +18 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +155 -0
- package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +123 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-auth-api-key/src/widgets/next.config.js +37 -0
- package/templates/typescript-auth-api-key/src/widgets/package.json +24 -0
- package/templates/typescript-auth-api-key/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-auth-api-key/tsconfig.json +23 -0
- package/templates/typescript-oauth/.env.example +91 -0
- package/templates/typescript-oauth/src/app.module.ts +89 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +127 -0
- package/templates/typescript-oauth/src/index.ts +74 -0
- package/templates/typescript-oauth/src/modules/demo/demo.module.ts +16 -0
- package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +190 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +37 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-starter/.env.example +4 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +27 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +60 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-starter/src/widgets/next.config.js +37 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-starter/tsconfig.json +23 -0
- package/LICENSE_URLS_UPDATE_COMPLETE.md +0 -388
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Addresses Resources
|
|
5
|
+
* Provides address-related resource endpoints
|
|
6
|
+
*/
|
|
7
|
+
export class AddressesResources {
|
|
8
|
+
/**
|
|
9
|
+
* Address schema resource
|
|
10
|
+
*/
|
|
11
|
+
@Resource({
|
|
12
|
+
uri: 'addresses://schema',
|
|
13
|
+
name: 'Address Schema',
|
|
14
|
+
description: 'Schema definition for shipping address objects',
|
|
15
|
+
mimeType: 'application/json',
|
|
16
|
+
})
|
|
17
|
+
async getAddressSchema(context: ExecutionContext) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'json' as const,
|
|
20
|
+
data: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string', format: 'uuid', description: 'Address ID' },
|
|
24
|
+
user_id: { type: 'string', description: 'User ID' },
|
|
25
|
+
full_name: { type: 'string', description: 'Full name for delivery' },
|
|
26
|
+
street: { type: 'string', description: 'Street address' },
|
|
27
|
+
city: { type: 'string', description: 'City' },
|
|
28
|
+
state: { type: 'string', description: 'State/Province' },
|
|
29
|
+
zip_code: { type: 'string', description: 'ZIP/Postal code' },
|
|
30
|
+
country: { type: 'string', default: 'USA', description: 'Country' },
|
|
31
|
+
phone: { type: 'string', description: 'Contact phone number' },
|
|
32
|
+
is_default: { type: 'boolean', description: 'Is default shipping address' },
|
|
33
|
+
created_at: { type: 'string', format: 'date-time', description: 'Address creation time' },
|
|
34
|
+
},
|
|
35
|
+
required: ['full_name', 'street', 'city', 'state', 'zip_code', 'phone'],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { ToolDecorator as Tool, Widget, UseGuards, z, ExecutionContext } from 'nitrostack';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { getDatabase } from '../../db/database.js';
|
|
4
|
+
import { JWTGuard } from '../../guards/jwt.guard.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Addresses Tools
|
|
8
|
+
* Handles user shipping addresses (requires authentication)
|
|
9
|
+
*/
|
|
10
|
+
export class AddressesTools {
|
|
11
|
+
/**
|
|
12
|
+
* Add new address - Requires authentication
|
|
13
|
+
*/
|
|
14
|
+
@Tool({
|
|
15
|
+
name: 'add_address',
|
|
16
|
+
description: 'Add a new shipping address. Requires authentication.',
|
|
17
|
+
inputSchema: z.object({
|
|
18
|
+
full_name: z.string().describe('Full name for delivery'),
|
|
19
|
+
street: z.string().describe('Street address'),
|
|
20
|
+
city: z.string().describe('City'),
|
|
21
|
+
state: z.string().describe('State/Province'),
|
|
22
|
+
zip_code: z.string().describe('ZIP/Postal code'),
|
|
23
|
+
country: z.string().default('USA').describe('Country'),
|
|
24
|
+
phone: z.string().describe('Contact phone number'),
|
|
25
|
+
is_default: z.boolean().default(false).describe('Set as default address'),
|
|
26
|
+
}),
|
|
27
|
+
examples: {
|
|
28
|
+
request: {
|
|
29
|
+
full_name: 'Emily Johnson',
|
|
30
|
+
street: '123 Main Street',
|
|
31
|
+
city: 'San Francisco',
|
|
32
|
+
state: 'CA',
|
|
33
|
+
zip_code: '94102',
|
|
34
|
+
country: 'USA',
|
|
35
|
+
phone: '+1-555-0100',
|
|
36
|
+
is_default: true
|
|
37
|
+
},
|
|
38
|
+
response: {
|
|
39
|
+
message: 'Address added successfully!',
|
|
40
|
+
addressId: 'addr-1',
|
|
41
|
+
isDefault: true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
@Widget('address-added')
|
|
46
|
+
@UseGuards(JWTGuard)
|
|
47
|
+
async addAddress(input: any, context: ExecutionContext) {
|
|
48
|
+
const db = getDatabase();
|
|
49
|
+
const userId = context.auth?.subject;
|
|
50
|
+
const addressId = uuidv4();
|
|
51
|
+
|
|
52
|
+
// If this is set as default, unset other defaults
|
|
53
|
+
if (input.is_default) {
|
|
54
|
+
db.prepare(`
|
|
55
|
+
UPDATE addresses SET is_default = 0 WHERE user_id = ?
|
|
56
|
+
`).run(userId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
db.prepare(`
|
|
60
|
+
INSERT INTO addresses (id, user_id, full_name, street, city, state, zip_code, country, phone, is_default)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
62
|
+
`).run(
|
|
63
|
+
addressId,
|
|
64
|
+
userId,
|
|
65
|
+
input.full_name,
|
|
66
|
+
input.street,
|
|
67
|
+
input.city,
|
|
68
|
+
input.state,
|
|
69
|
+
input.zip_code,
|
|
70
|
+
input.country,
|
|
71
|
+
input.phone,
|
|
72
|
+
input.is_default ? 1 : 0
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
context.logger.info(`Address added: ${input.city}, ${input.state}`);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
message: 'Address added successfully!',
|
|
79
|
+
addressId,
|
|
80
|
+
isDefault: input.is_default,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get all addresses - Requires authentication
|
|
86
|
+
*/
|
|
87
|
+
@Tool({
|
|
88
|
+
name: 'get_addresses',
|
|
89
|
+
description: 'Get all saved shipping addresses. Requires authentication.',
|
|
90
|
+
inputSchema: z.object({}),
|
|
91
|
+
examples: {
|
|
92
|
+
request: {},
|
|
93
|
+
response: {
|
|
94
|
+
addresses: [
|
|
95
|
+
{
|
|
96
|
+
id: 'addr-1',
|
|
97
|
+
full_name: 'Emily Johnson',
|
|
98
|
+
street: '123 Main Street',
|
|
99
|
+
city: 'San Francisco',
|
|
100
|
+
state: 'CA',
|
|
101
|
+
zip_code: '94102',
|
|
102
|
+
country: 'USA',
|
|
103
|
+
phone: '+1-555-0100',
|
|
104
|
+
is_default: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'addr-2',
|
|
108
|
+
full_name: 'Emily Johnson',
|
|
109
|
+
street: '456 Office Blvd',
|
|
110
|
+
city: 'San Francisco',
|
|
111
|
+
state: 'CA',
|
|
112
|
+
zip_code: '94103',
|
|
113
|
+
country: 'USA',
|
|
114
|
+
phone: '+1-555-0100',
|
|
115
|
+
is_default: false
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
@Widget('address-list')
|
|
122
|
+
@UseGuards(JWTGuard)
|
|
123
|
+
async getAddresses(input: any, context: ExecutionContext) {
|
|
124
|
+
const db = getDatabase();
|
|
125
|
+
const userId = context.auth?.subject;
|
|
126
|
+
|
|
127
|
+
const addresses: any[] = db.prepare(`
|
|
128
|
+
SELECT * FROM addresses WHERE user_id = ? ORDER BY is_default DESC, created_at DESC
|
|
129
|
+
`).all(userId);
|
|
130
|
+
|
|
131
|
+
context.logger.info(`Retrieved ${addresses.length} addresses`);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
addresses: addresses.map(addr => ({
|
|
135
|
+
...addr,
|
|
136
|
+
is_default: addr.is_default === 1,
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Set default address - Requires authentication
|
|
143
|
+
*/
|
|
144
|
+
@Tool({
|
|
145
|
+
name: 'set_default_address',
|
|
146
|
+
description: 'Set an address as the default shipping address. Requires authentication.',
|
|
147
|
+
examples: {
|
|
148
|
+
request: {
|
|
149
|
+
address_id: 'addr-2'
|
|
150
|
+
},
|
|
151
|
+
response: {
|
|
152
|
+
message: 'Default address updated!',
|
|
153
|
+
addressId: 'addr-2'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
inputSchema: z.object({
|
|
157
|
+
address_id: z.string().describe('The ID of the address to set as default'),
|
|
158
|
+
}),
|
|
159
|
+
})
|
|
160
|
+
@Widget('address-updated')
|
|
161
|
+
@UseGuards(JWTGuard)
|
|
162
|
+
async setDefaultAddress(input: any, context: ExecutionContext) {
|
|
163
|
+
const db = getDatabase();
|
|
164
|
+
const userId = context.auth?.subject;
|
|
165
|
+
|
|
166
|
+
// Verify address belongs to user
|
|
167
|
+
const address: any = db.prepare(`
|
|
168
|
+
SELECT * FROM addresses WHERE id = ? AND user_id = ?
|
|
169
|
+
`).get(input.address_id, userId);
|
|
170
|
+
|
|
171
|
+
if (!address) {
|
|
172
|
+
throw new Error('Address not found or you don\'t have access to it.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Unset all defaults
|
|
176
|
+
db.prepare(`
|
|
177
|
+
UPDATE addresses SET is_default = 0 WHERE user_id = ?
|
|
178
|
+
`).run(userId);
|
|
179
|
+
|
|
180
|
+
// Set new default
|
|
181
|
+
db.prepare(`
|
|
182
|
+
UPDATE addresses SET is_default = 1 WHERE id = ?
|
|
183
|
+
`).run(input.address_id);
|
|
184
|
+
|
|
185
|
+
context.logger.info(`Set default address: ${address.city}, ${address.state}`);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
message: 'Default address updated!',
|
|
189
|
+
address: {
|
|
190
|
+
...address,
|
|
191
|
+
is_default: true,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete address - Requires authentication
|
|
198
|
+
*/
|
|
199
|
+
@Tool({
|
|
200
|
+
name: 'delete_address',
|
|
201
|
+
description: 'Delete a shipping address. Requires authentication.',
|
|
202
|
+
inputSchema: z.object({
|
|
203
|
+
address_id: z.string().describe('The ID of the address to delete'),
|
|
204
|
+
}),
|
|
205
|
+
examples: {
|
|
206
|
+
request: {
|
|
207
|
+
address_id: 'addr-2'
|
|
208
|
+
},
|
|
209
|
+
response: {
|
|
210
|
+
message: 'Address deleted successfully!',
|
|
211
|
+
addressId: 'addr-2'
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
@Widget('address-deleted')
|
|
216
|
+
@UseGuards(JWTGuard)
|
|
217
|
+
async deleteAddress(input: any, context: ExecutionContext) {
|
|
218
|
+
const db = getDatabase();
|
|
219
|
+
const userId = context.auth?.subject;
|
|
220
|
+
|
|
221
|
+
// Verify address belongs to user
|
|
222
|
+
const address: any = db.prepare(`
|
|
223
|
+
SELECT * FROM addresses WHERE id = ? AND user_id = ?
|
|
224
|
+
`).get(input.address_id, userId);
|
|
225
|
+
|
|
226
|
+
if (!address) {
|
|
227
|
+
throw new Error('Address not found or you don\'t have access to it.');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
db.prepare(`
|
|
231
|
+
DELETE FROM addresses WHERE id = ?
|
|
232
|
+
`).run(input.address_id);
|
|
233
|
+
|
|
234
|
+
context.logger.info(`Deleted address: ${address.city}, ${address.state}`);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
message: 'Address deleted successfully!',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { AuthTools } from './auth.tools.js';
|
|
3
|
+
import { AuthResources } from './auth.resources.js';
|
|
4
|
+
import { AuthPrompts } from './auth.prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Authentication Module
|
|
8
|
+
* Provides login, user profile tools, resources, and prompts
|
|
9
|
+
*/
|
|
10
|
+
@Module({
|
|
11
|
+
name: 'auth',
|
|
12
|
+
description: 'Authentication and user management',
|
|
13
|
+
controllers: [AuthTools, AuthResources, AuthPrompts],
|
|
14
|
+
})
|
|
15
|
+
export class AuthModule {}
|
|
16
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Authentication Prompts
|
|
5
|
+
* Provides helpful prompts for authentication-related tasks
|
|
6
|
+
*/
|
|
7
|
+
export class AuthPrompts {
|
|
8
|
+
/**
|
|
9
|
+
* Authentication help prompt
|
|
10
|
+
*/
|
|
11
|
+
@Prompt({
|
|
12
|
+
name: 'auth-help',
|
|
13
|
+
description: 'Get help with authentication and login process',
|
|
14
|
+
arguments: [
|
|
15
|
+
{
|
|
16
|
+
name: 'issue',
|
|
17
|
+
description: 'The authentication issue you are facing',
|
|
18
|
+
required: false,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
})
|
|
22
|
+
async authHelp(args: Record<string, any>, context: ExecutionContext) {
|
|
23
|
+
const issue = args.issue || 'general';
|
|
24
|
+
|
|
25
|
+
const baseMessages = [
|
|
26
|
+
{
|
|
27
|
+
role: 'user' as const,
|
|
28
|
+
content: `I need help with authentication in the e-commerce system. My issue: ${issue}`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
role: 'assistant' as const,
|
|
32
|
+
content: `I'll help you with authentication. Here's what you need to know:
|
|
33
|
+
|
|
34
|
+
**Login Process:**
|
|
35
|
+
1. Use the \`login\` tool with email and password
|
|
36
|
+
2. You'll receive a JWT token valid for 24 hours
|
|
37
|
+
3. Include this token in subsequent requests
|
|
38
|
+
|
|
39
|
+
**Test Accounts:**
|
|
40
|
+
- alice@example.com / password123
|
|
41
|
+
- bob@example.com / password123
|
|
42
|
+
- charlie@example.com / password123
|
|
43
|
+
|
|
44
|
+
**Including Token:**
|
|
45
|
+
- In Inspector: Paste token in "JWT Token" field
|
|
46
|
+
- Or use: Authorization: Bearer <your-token>
|
|
47
|
+
|
|
48
|
+
**Protected Tools:**
|
|
49
|
+
The following tools require authentication:
|
|
50
|
+
- whoami - Get your profile
|
|
51
|
+
- add_to_cart - Add items to cart
|
|
52
|
+
- view_cart - View your cart
|
|
53
|
+
- create_order - Place orders
|
|
54
|
+
- get_order_history - View order history
|
|
55
|
+
- add_address - Manage addresses
|
|
56
|
+
|
|
57
|
+
**Common Issues:**
|
|
58
|
+
${issue === 'invalid-token' ? '- Token expired: Login again to get a new token\n- Invalid format: Ensure you copied the complete token' : ''}
|
|
59
|
+
${issue === 'unauthorized' ? '- Missing token: Make sure to login first\n- Wrong credentials: Check your email and password' : ''}
|
|
60
|
+
${issue === 'general' ? '- Token expired? Login again\n- Forgot which email? Use test accounts above\n- Need to test? All test users have password: password123' : ''}
|
|
61
|
+
|
|
62
|
+
**Security Notes:**
|
|
63
|
+
- Tokens expire after 24 hours for security
|
|
64
|
+
- Passwords are hashed with bcrypt
|
|
65
|
+
- Never share your token with others`,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return baseMessages;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* JWT token explanation prompt
|
|
74
|
+
*/
|
|
75
|
+
@Prompt({
|
|
76
|
+
name: 'jwt-explained',
|
|
77
|
+
description: 'Understand how JWT authentication works in this system',
|
|
78
|
+
})
|
|
79
|
+
async jwtExplained(args: Record<string, any>, context: ExecutionContext) {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
role: 'user' as const,
|
|
83
|
+
content: 'Explain how JWT authentication works in this e-commerce system',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
role: 'assistant' as const,
|
|
87
|
+
content: `**JWT Authentication in E-commerce MCP Server**
|
|
88
|
+
|
|
89
|
+
**What is JWT?**
|
|
90
|
+
JWT (JSON Web Token) is a secure way to transmit information between parties. It's a token that contains:
|
|
91
|
+
- User identity (subject)
|
|
92
|
+
- User information (email, name)
|
|
93
|
+
- Expiration time
|
|
94
|
+
- Digital signature
|
|
95
|
+
|
|
96
|
+
**How It Works Here:**
|
|
97
|
+
|
|
98
|
+
1. **Login** (Public)
|
|
99
|
+
- User provides email + password
|
|
100
|
+
- Server verifies with bcrypt
|
|
101
|
+
- Server generates JWT token
|
|
102
|
+
- Token valid for 24 hours
|
|
103
|
+
|
|
104
|
+
2. **Using Protected Tools**
|
|
105
|
+
- Client includes token in request
|
|
106
|
+
- Server automatically validates token
|
|
107
|
+
- If valid: grants access
|
|
108
|
+
- If invalid/missing: denies access
|
|
109
|
+
|
|
110
|
+
3. **Token Contents**
|
|
111
|
+
\`\`\`json
|
|
112
|
+
{
|
|
113
|
+
"sub": "user-id",
|
|
114
|
+
"email": "alice@example.com",
|
|
115
|
+
"name": "Alice Johnson",
|
|
116
|
+
"iat": 1234567890,
|
|
117
|
+
"exp": 1234654290
|
|
118
|
+
}
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
**Security Features:**
|
|
122
|
+
- ✅ Tokens are signed (can't be tampered)
|
|
123
|
+
- ✅ Passwords hashed with bcrypt
|
|
124
|
+
- ✅ Tokens expire after 24 hours
|
|
125
|
+
- ✅ No password storage in tokens
|
|
126
|
+
|
|
127
|
+
**Developer Experience:**
|
|
128
|
+
With V3 decorators, authentication is automatic:
|
|
129
|
+
\`\`\`typescript
|
|
130
|
+
@UseGuards(JWTGuard)
|
|
131
|
+
async myProtectedTool(input, context) {
|
|
132
|
+
// context.auth is already populated!
|
|
133
|
+
const userId = context.auth.subject;
|
|
134
|
+
}
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
**No Manual Checking Needed!**
|
|
138
|
+
The \`@UseGuards(JWTGuard)\` decorator handles:
|
|
139
|
+
- Token extraction
|
|
140
|
+
- Signature validation
|
|
141
|
+
- Expiry checking
|
|
142
|
+
- Context population`,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';
|
|
2
|
+
import { getDatabase } from '../../db/database.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Authentication Resources
|
|
6
|
+
* Provides auth-related resource endpoints
|
|
7
|
+
*/
|
|
8
|
+
export class AuthResources {
|
|
9
|
+
/**
|
|
10
|
+
* User schema resource
|
|
11
|
+
*/
|
|
12
|
+
@Resource({
|
|
13
|
+
uri: 'auth://schema/user',
|
|
14
|
+
name: 'User Schema',
|
|
15
|
+
description: 'Schema definition for user objects in the authentication system',
|
|
16
|
+
mimeType: 'application/json',
|
|
17
|
+
})
|
|
18
|
+
async getUserSchema(context: ExecutionContext) {
|
|
19
|
+
return {
|
|
20
|
+
type: 'json' as const,
|
|
21
|
+
data: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
id: { type: 'string', format: 'uuid', description: 'Unique user identifier' },
|
|
25
|
+
email: { type: 'string', format: 'email', description: 'User email address' },
|
|
26
|
+
name: { type: 'string', description: 'Full name' },
|
|
27
|
+
profile_picture: { type: 'string', format: 'uri', nullable: true, description: 'Profile picture URL' },
|
|
28
|
+
created_at: { type: 'string', format: 'date-time', description: 'Account creation timestamp' },
|
|
29
|
+
},
|
|
30
|
+
required: ['id', 'email', 'name'],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Available test users resource
|
|
37
|
+
*/
|
|
38
|
+
@Resource({
|
|
39
|
+
uri: 'auth://test-users',
|
|
40
|
+
name: 'Test Users',
|
|
41
|
+
description: 'List of available test users for the demo',
|
|
42
|
+
mimeType: 'application/json',
|
|
43
|
+
})
|
|
44
|
+
async getTestUsers(context: ExecutionContext) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'json' as const,
|
|
47
|
+
data: {
|
|
48
|
+
users: [
|
|
49
|
+
{ email: 'alice@example.com', password: 'password123', name: 'Alice Johnson' },
|
|
50
|
+
{ email: 'bob@example.com', password: 'password123', name: 'Bob Smith' },
|
|
51
|
+
{ email: 'charlie@example.com', password: 'password123', name: 'Charlie Brown' },
|
|
52
|
+
],
|
|
53
|
+
note: 'All test users have the same password: password123',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Authentication statistics
|
|
60
|
+
*/
|
|
61
|
+
@Resource({
|
|
62
|
+
uri: 'auth://stats',
|
|
63
|
+
name: 'Auth Statistics',
|
|
64
|
+
description: 'Authentication system statistics',
|
|
65
|
+
mimeType: 'application/json',
|
|
66
|
+
})
|
|
67
|
+
async getAuthStats(context: ExecutionContext) {
|
|
68
|
+
const db = getDatabase();
|
|
69
|
+
|
|
70
|
+
const totalUsers: any = db.prepare('SELECT COUNT(*) as count FROM users').get();
|
|
71
|
+
const recentLogins: any[] = db.prepare('SELECT email, created_at FROM users ORDER BY created_at DESC LIMIT 5').all();
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
type: 'json' as const,
|
|
75
|
+
data: {
|
|
76
|
+
totalUsers: totalUsers.count,
|
|
77
|
+
recentUsers: recentLogins,
|
|
78
|
+
authMethod: 'JWT with bcrypt password hashing',
|
|
79
|
+
tokenExpiry: '24 hours',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ToolDecorator as Tool, Widget, UseGuards, z, ExecutionContext } from 'nitrostack';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import { getDatabase } from '../../db/database.js';
|
|
4
|
+
import { generateJWT } from 'nitrostack';
|
|
5
|
+
import { JWTGuard } from '../../guards/jwt.guard.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Authentication Tools
|
|
9
|
+
* Handles user authentication and profile management
|
|
10
|
+
*/
|
|
11
|
+
export class AuthTools {
|
|
12
|
+
/**
|
|
13
|
+
* Login tool - Authenticate user and return JWT token
|
|
14
|
+
* No authentication required (public endpoint)
|
|
15
|
+
*/
|
|
16
|
+
@Tool({
|
|
17
|
+
name: 'login',
|
|
18
|
+
description: 'Login with email and password to get a JWT token. Use this token in the Authorization header for authenticated requests.',
|
|
19
|
+
inputSchema: z.object({
|
|
20
|
+
email: z.string().email().describe('User email address'),
|
|
21
|
+
password: z.string().describe('User password'),
|
|
22
|
+
}),
|
|
23
|
+
examples: {
|
|
24
|
+
request: {
|
|
25
|
+
email: 'emily.johnson@x.dummyjson.com',
|
|
26
|
+
password: 'password123'
|
|
27
|
+
},
|
|
28
|
+
response: {
|
|
29
|
+
message: 'Login successful!',
|
|
30
|
+
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
31
|
+
user: {
|
|
32
|
+
id: 'user-emily',
|
|
33
|
+
email: 'emily.johnson@x.dummyjson.com',
|
|
34
|
+
name: 'Emily Johnson'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
@Widget('login-result')
|
|
40
|
+
async login(input: any, context: ExecutionContext) {
|
|
41
|
+
const db = getDatabase();
|
|
42
|
+
|
|
43
|
+
// Find user
|
|
44
|
+
const user: any = db.prepare('SELECT * FROM users WHERE email = ?').get(input.email);
|
|
45
|
+
|
|
46
|
+
if (!user) {
|
|
47
|
+
throw new Error('Invalid email or password');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Verify password
|
|
51
|
+
const valid = bcrypt.compareSync(input.password, user.password_hash);
|
|
52
|
+
|
|
53
|
+
if (!valid) {
|
|
54
|
+
throw new Error('Invalid email or password');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate JWT token
|
|
58
|
+
const token = generateJWT({
|
|
59
|
+
secret: process.env.JWT_SECRET!,
|
|
60
|
+
payload: {
|
|
61
|
+
sub: user.id,
|
|
62
|
+
email: user.email,
|
|
63
|
+
name: user.name,
|
|
64
|
+
},
|
|
65
|
+
expiresIn: '24h',
|
|
66
|
+
audience: process.env.JWT_AUDIENCE || 'ecommerce-mcp-server',
|
|
67
|
+
issuer: process.env.JWT_ISSUER || 'ecommerce-app',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
context.logger.info(`User logged in: ${user.email}`);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
message: 'Login successful!',
|
|
74
|
+
token,
|
|
75
|
+
user: {
|
|
76
|
+
id: user.id,
|
|
77
|
+
email: user.email,
|
|
78
|
+
name: user.name,
|
|
79
|
+
},
|
|
80
|
+
instructions: {
|
|
81
|
+
step1: 'Copy the token above',
|
|
82
|
+
step2: 'In the inspector, go to Chat tab',
|
|
83
|
+
step3: 'Paste the token in the "JWT Token" field (if available)',
|
|
84
|
+
step4: 'Or use: Authorization: Bearer <token>',
|
|
85
|
+
expiresIn: '24 hours',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get current user information
|
|
92
|
+
* Requires JWT authentication
|
|
93
|
+
*/
|
|
94
|
+
@Tool({
|
|
95
|
+
name: 'whoami',
|
|
96
|
+
description: 'Get information about the currently authenticated user. Requires authentication.',
|
|
97
|
+
inputSchema: z.object({}),
|
|
98
|
+
examples: {
|
|
99
|
+
request: {},
|
|
100
|
+
response: {
|
|
101
|
+
user: {
|
|
102
|
+
id: 'user-emily',
|
|
103
|
+
email: 'emily.johnson@x.dummyjson.com',
|
|
104
|
+
name: 'Emily Johnson',
|
|
105
|
+
profile_picture: 'https://dummyjson.com/icon/emilys/128',
|
|
106
|
+
memberSince: '2024-01-01T00:00:00Z'
|
|
107
|
+
},
|
|
108
|
+
authenticated: true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
@Widget('whoami')
|
|
113
|
+
@UseGuards(JWTGuard)
|
|
114
|
+
async whoami(input: any, context: ExecutionContext) {
|
|
115
|
+
// User is already authenticated by JWTGuard
|
|
116
|
+
const userId = context.auth?.subject;
|
|
117
|
+
|
|
118
|
+
const db = getDatabase();
|
|
119
|
+
const user: any = db.prepare(`
|
|
120
|
+
SELECT id, email, name, profile_picture, created_at FROM users WHERE id = ?
|
|
121
|
+
`).get(userId);
|
|
122
|
+
|
|
123
|
+
if (!user) {
|
|
124
|
+
throw new Error('User not found');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
user: {
|
|
129
|
+
id: user.id,
|
|
130
|
+
email: user.email,
|
|
131
|
+
name: user.name,
|
|
132
|
+
profile_picture: user.profile_picture,
|
|
133
|
+
memberSince: user.created_at,
|
|
134
|
+
},
|
|
135
|
+
authenticated: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { CartTools } from './cart.tools.js';
|
|
3
|
+
import { CartResources } from './cart.resources.js';
|
|
4
|
+
import { CartPrompts } from './cart.prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cart Module
|
|
8
|
+
* Provides shopping cart management, resources, and prompts
|
|
9
|
+
*/
|
|
10
|
+
@Module({
|
|
11
|
+
name: 'cart',
|
|
12
|
+
description: 'Shopping cart management',
|
|
13
|
+
controllers: [CartTools, CartResources, CartPrompts],
|
|
14
|
+
})
|
|
15
|
+
export class CartModule {}
|
|
16
|
+
|