@vltpkg/vsr 0.0.0-27 → 0.0.0-28
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/DEPLOY.md +163 -0
- package/LICENSE +114 -10
- package/config.ts +221 -0
- package/dist/README.md +1 -1
- package/dist/bin/vsr.js +8 -6
- package/dist/index.js +3 -6
- package/dist/index.js.map +2 -2
- package/drizzle.config.js +40 -0
- package/info/COMPARISONS.md +37 -0
- package/info/CONFIGURATION.md +143 -0
- package/info/CONTRIBUTING.md +32 -0
- package/info/DATABASE_SETUP.md +108 -0
- package/info/GRANULAR_ACCESS_TOKENS.md +160 -0
- package/info/PROJECT_STRUCTURE.md +291 -0
- package/info/ROADMAP.md +27 -0
- package/info/SUPPORT.md +39 -0
- package/info/TESTING.md +301 -0
- package/info/USER_SUPPORT.md +31 -0
- package/package.json +49 -6
- package/scripts/build-assets.js +31 -0
- package/scripts/build-bin.js +63 -0
- package/src/assets/public/images/bg.png +0 -0
- package/src/assets/public/images/clients/logo-bun.png +0 -0
- package/src/assets/public/images/clients/logo-deno.png +0 -0
- package/src/assets/public/images/clients/logo-npm.png +0 -0
- package/src/assets/public/images/clients/logo-pnpm.png +0 -0
- package/src/assets/public/images/clients/logo-vlt.png +0 -0
- package/src/assets/public/images/clients/logo-yarn.png +0 -0
- package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
- package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
- package/src/assets/public/images/favicon/favicon.ico +0 -0
- package/src/assets/public/images/favicon/favicon.svg +3 -0
- package/src/assets/public/images/favicon/site.webmanifest +21 -0
- package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
- package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
- package/src/assets/public/styles/styles.css +231 -0
- package/src/bin/demo/package.json +6 -0
- package/src/bin/demo/vlt.json +1 -0
- package/src/bin/vsr.ts +496 -0
- package/src/db/client.ts +590 -0
- package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
- package/src/db/migrations/0000_initial.sql +29 -0
- package/src/db/migrations/0001_uuid_validation.sql +35 -0
- package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
- package/src/db/migrations/drop.sql +3 -0
- package/src/db/migrations/meta/0000_snapshot.json +104 -0
- package/src/db/migrations/meta/0001_snapshot.json +155 -0
- package/src/db/migrations/meta/_journal.json +20 -0
- package/src/db/schema.ts +43 -0
- package/src/index.ts +434 -0
- package/src/middleware/config.ts +79 -0
- package/src/middleware/telemetry.ts +43 -0
- package/src/queue/index.ts +97 -0
- package/src/routes/access.ts +852 -0
- package/src/routes/docs.ts +63 -0
- package/src/routes/misc.ts +469 -0
- package/src/routes/packages.ts +2823 -0
- package/src/routes/ping.ts +39 -0
- package/src/routes/search.ts +131 -0
- package/src/routes/static.ts +74 -0
- package/src/routes/tokens.ts +259 -0
- package/src/routes/users.ts +68 -0
- package/src/utils/auth.ts +202 -0
- package/src/utils/cache.ts +587 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/database.ts +69 -0
- package/src/utils/docs.ts +146 -0
- package/src/utils/packages.ts +453 -0
- package/src/utils/response.ts +125 -0
- package/src/utils/routes.ts +64 -0
- package/src/utils/spa.ts +52 -0
- package/src/utils/tracing.ts +52 -0
- package/src/utils/upstream.ts +172 -0
- package/tsconfig.json +16 -0
- package/tsconfig.worker.json +3 -0
- package/typedoc.mjs +2 -0
- package/types.ts +598 -0
- package/vitest.config.ts +25 -0
- package/vlt.json.example +56 -0
- package/wrangler.json +65 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { URL } from '../../config.ts'
|
|
2
|
+
import { createRoute, z } from '@hono/zod-openapi'
|
|
3
|
+
import type { Context } from 'hono'
|
|
4
|
+
|
|
5
|
+
export const pingRoute = createRoute({
|
|
6
|
+
method: 'get',
|
|
7
|
+
path: '/-/ping',
|
|
8
|
+
tags: ['Health Check'],
|
|
9
|
+
summary: 'Ping',
|
|
10
|
+
description: `Check if the server is alive
|
|
11
|
+
\`\`\`bash
|
|
12
|
+
$ npm ping
|
|
13
|
+
npm notice PING ${URL}
|
|
14
|
+
npm notice PONG 13ms
|
|
15
|
+
\`\`\``,
|
|
16
|
+
request: {},
|
|
17
|
+
responses: {
|
|
18
|
+
200: {
|
|
19
|
+
content: {
|
|
20
|
+
'application/json': {
|
|
21
|
+
schema: z.object({}),
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
headers: z.object({
|
|
25
|
+
'npm-notice': z.string().openapi({
|
|
26
|
+
description: 'Contains "PONG" for npm client compatibility',
|
|
27
|
+
example: 'PONG',
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
description: 'Server is alive',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Ping route handler
|
|
36
|
+
export function handlePing(c: Context): Response {
|
|
37
|
+
c.header('npm-notice', 'PONG')
|
|
38
|
+
return c.json({}, 200)
|
|
39
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { HonoContext } from '../../types.ts'
|
|
2
|
+
import { createRoute, z } from '@hono/zod-openapi'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Search for packages by text query
|
|
6
|
+
*/
|
|
7
|
+
export async function searchPackages(c: HonoContext) {
|
|
8
|
+
try {
|
|
9
|
+
const text = c.req.query('text')
|
|
10
|
+
|
|
11
|
+
if (!text) {
|
|
12
|
+
return c.json(
|
|
13
|
+
{
|
|
14
|
+
error: 'Missing required parameter "text"',
|
|
15
|
+
},
|
|
16
|
+
400,
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Use the existing database search function
|
|
21
|
+
const results = await c.get('db').searchPackages(text)
|
|
22
|
+
|
|
23
|
+
return c.json(results)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// TODO: Replace with proper logging system
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.error('Search error:', error)
|
|
28
|
+
return c.json(
|
|
29
|
+
{
|
|
30
|
+
error: 'Internal server error',
|
|
31
|
+
},
|
|
32
|
+
500,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Route definition for OpenAPI documentation
|
|
38
|
+
export const searchPackagesRoute = createRoute({
|
|
39
|
+
method: 'get',
|
|
40
|
+
path: '/-/search',
|
|
41
|
+
tags: ['Search'],
|
|
42
|
+
summary: 'Search Packages',
|
|
43
|
+
description: `Search for packages by text query
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
$ npm search react
|
|
46
|
+
\`\`\``,
|
|
47
|
+
request: {
|
|
48
|
+
query: z.object({
|
|
49
|
+
text: z.string().describe('Search query string'),
|
|
50
|
+
size: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Number of results to return (default: 20)'),
|
|
54
|
+
from: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Offset for pagination (default: 0)'),
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
responses: {
|
|
61
|
+
200: {
|
|
62
|
+
content: {
|
|
63
|
+
'application/json': {
|
|
64
|
+
schema: z.object({
|
|
65
|
+
objects: z.array(
|
|
66
|
+
z.object({
|
|
67
|
+
package: z.object({
|
|
68
|
+
name: z.string(),
|
|
69
|
+
version: z.string(),
|
|
70
|
+
description: z.string().optional(),
|
|
71
|
+
keywords: z.array(z.string()).optional(),
|
|
72
|
+
date: z.string().optional(),
|
|
73
|
+
links: z
|
|
74
|
+
.object({
|
|
75
|
+
npm: z.string().optional(),
|
|
76
|
+
homepage: z.string().optional(),
|
|
77
|
+
repository: z.string().optional(),
|
|
78
|
+
bugs: z.string().optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional(),
|
|
81
|
+
author: z
|
|
82
|
+
.object({
|
|
83
|
+
name: z.string(),
|
|
84
|
+
email: z.string().optional(),
|
|
85
|
+
})
|
|
86
|
+
.optional(),
|
|
87
|
+
publisher: z
|
|
88
|
+
.object({
|
|
89
|
+
username: z.string(),
|
|
90
|
+
email: z.string().optional(),
|
|
91
|
+
})
|
|
92
|
+
.optional(),
|
|
93
|
+
maintainers: z
|
|
94
|
+
.array(
|
|
95
|
+
z.object({
|
|
96
|
+
username: z.string(),
|
|
97
|
+
email: z.string().optional(),
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
.optional(),
|
|
101
|
+
}),
|
|
102
|
+
score: z.object({
|
|
103
|
+
final: z.number(),
|
|
104
|
+
detail: z.object({
|
|
105
|
+
quality: z.number(),
|
|
106
|
+
popularity: z.number(),
|
|
107
|
+
maintenance: z.number(),
|
|
108
|
+
}),
|
|
109
|
+
}),
|
|
110
|
+
searchScore: z.number(),
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
total: z.number(),
|
|
114
|
+
time: z.string(),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
description: 'Search results',
|
|
119
|
+
},
|
|
120
|
+
400: {
|
|
121
|
+
content: {
|
|
122
|
+
'application/json': {
|
|
123
|
+
schema: z.object({
|
|
124
|
+
error: z.string(),
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
description: 'Bad request - missing text parameter',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { HonoContext } from '../../types.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handles static asset serving
|
|
5
|
+
*/
|
|
6
|
+
export const handleStaticAssets = async (c: HonoContext) => {
|
|
7
|
+
try {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
9
|
+
const response = (await c.env.ASSETS.fetch(c.req.raw)) as Response
|
|
10
|
+
|
|
11
|
+
// If the ASSETS binding returns a 404, return a proper 404 response
|
|
12
|
+
// with mutable headers to avoid the secure headers middleware conflict
|
|
13
|
+
if (response.status === 404) {
|
|
14
|
+
return await c.notFound()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// For other non-200 responses, create a new response to avoid header conflicts
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
return c.text('Asset not available', response.status as any)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return response
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// TODO: Replace with proper logging system
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.error('Error serving static asset:', error)
|
|
27
|
+
return c.text('Internal Server Error', 500)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handles favicon requests
|
|
33
|
+
*/
|
|
34
|
+
export const handleFavicon = async (c: HonoContext) => {
|
|
35
|
+
// Redirect to the correct favicon path
|
|
36
|
+
return c.redirect('/public/images/favicon/favicon.ico', 301)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Handles robots.txt requests
|
|
41
|
+
*/
|
|
42
|
+
export function handleRobots(c: HonoContext) {
|
|
43
|
+
return c.text(`User-agent: *
|
|
44
|
+
Allow: /
|
|
45
|
+
|
|
46
|
+
Sitemap: ${c.req.url.replace(/\/robots\.txt$/, '/sitemap.xml')}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handles manifest.json requests for PWA
|
|
51
|
+
*/
|
|
52
|
+
export function handleManifest(c: HonoContext) {
|
|
53
|
+
return c.json({
|
|
54
|
+
name: 'VLT Serverless Registry',
|
|
55
|
+
short_name: 'VSR',
|
|
56
|
+
description: 'A serverless npm registry',
|
|
57
|
+
start_url: '/',
|
|
58
|
+
display: 'standalone',
|
|
59
|
+
background_color: '#ffffff',
|
|
60
|
+
theme_color: '#000000',
|
|
61
|
+
icons: [
|
|
62
|
+
{
|
|
63
|
+
src: '/public/images/favicon/web-app-manifest-192x192.png',
|
|
64
|
+
sizes: '192x192',
|
|
65
|
+
type: 'image/png',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
src: '/public/images/favicon/web-app-manifest-512x512.png',
|
|
69
|
+
sizes: '512x512',
|
|
70
|
+
type: 'image/png',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// import { v4 as uuidv4 } from 'uuid' // Removed unused import
|
|
2
|
+
import { getTokenFromHeader } from '../utils/auth.ts'
|
|
3
|
+
import type {
|
|
4
|
+
HonoContext,
|
|
5
|
+
DatabaseOperations,
|
|
6
|
+
TokenScope,
|
|
7
|
+
} from '../../types.ts'
|
|
8
|
+
import { createRoute, z } from '@hono/zod-openapi'
|
|
9
|
+
|
|
10
|
+
// Helper function to get typed database from context
|
|
11
|
+
function getDb(c: HonoContext): DatabaseOperations {
|
|
12
|
+
return c.get('db')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getToken(c: HonoContext) {
|
|
16
|
+
const token = c.req.param('token')
|
|
17
|
+
if (!token) {
|
|
18
|
+
return c.json({ error: 'Token parameter required' }, 400)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const tokenData = await getDb(c).getToken(token)
|
|
22
|
+
if (!tokenData) {
|
|
23
|
+
return c.json({ error: 'Token not found' }, 404)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return c.json(tokenData)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function postToken(c: HonoContext) {
|
|
30
|
+
try {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
32
|
+
const body = await c.req.json()
|
|
33
|
+
const authToken = getTokenFromHeader(c)
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
36
|
+
if (!body.token || !body.uuid || !body.scope) {
|
|
37
|
+
return c.json(
|
|
38
|
+
{ error: 'Missing required fields: token, uuid, scope' },
|
|
39
|
+
400,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Use the enhanced database operation that includes validation
|
|
44
|
+
await getDb(c).upsertToken(
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
46
|
+
body.token as string,
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
48
|
+
body.uuid as string,
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
50
|
+
body.scope as TokenScope[],
|
|
51
|
+
authToken ?? undefined,
|
|
52
|
+
)
|
|
53
|
+
return c.json({ success: true })
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const err = error as Error
|
|
56
|
+
// Handle validation errors
|
|
57
|
+
if (err.message.includes('Invalid uuid')) {
|
|
58
|
+
return c.json({ error: err.message }, 400)
|
|
59
|
+
} else if (err.message.includes('Unauthorized')) {
|
|
60
|
+
return c.json({ error: 'Unauthorized' }, 401)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle other errors - log to monitoring system instead of console
|
|
64
|
+
return c.json({ error: 'Internal server error' }, 500)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// scope is optional (only for privileged tokens) - ex. "read:@scope/pkg" or "read+write:@scope/pkg"
|
|
69
|
+
export async function putToken(c: HonoContext) {
|
|
70
|
+
try {
|
|
71
|
+
const token = c.req.param('token')
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
73
|
+
const body = await c.req.json()
|
|
74
|
+
const authToken = getTokenFromHeader(c)
|
|
75
|
+
|
|
76
|
+
if (!token) {
|
|
77
|
+
return c.json({ error: 'Token parameter required' }, 400)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
81
|
+
if (!body.uuid || !body.scope) {
|
|
82
|
+
return c.json(
|
|
83
|
+
{ error: 'Missing required fields: uuid, scope' },
|
|
84
|
+
400,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Use the enhanced database operation that includes validation
|
|
89
|
+
await getDb(c).upsertToken(
|
|
90
|
+
token,
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
92
|
+
body.uuid as string,
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
94
|
+
body.scope as TokenScope[],
|
|
95
|
+
authToken ?? undefined,
|
|
96
|
+
)
|
|
97
|
+
return c.json({ success: true })
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const err = error as Error
|
|
100
|
+
// Handle validation errors
|
|
101
|
+
if (err.message.includes('Invalid uuid')) {
|
|
102
|
+
return c.json({ error: err.message }, 400)
|
|
103
|
+
} else if (err.message.includes('Unauthorized')) {
|
|
104
|
+
return c.json({ error: 'Unauthorized' }, 401)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle other errors - log to monitoring system instead of console
|
|
108
|
+
return c.json({ error: 'Internal server error' }, 500)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function deleteToken(c: HonoContext) {
|
|
113
|
+
try {
|
|
114
|
+
const token = c.req.param('token')
|
|
115
|
+
const authToken = getTokenFromHeader(c)
|
|
116
|
+
|
|
117
|
+
if (!token) {
|
|
118
|
+
return c.json({ error: 'Token parameter required' }, 400)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Use the enhanced database operation that includes validation
|
|
122
|
+
await getDb(c).deleteToken(token, authToken ?? undefined)
|
|
123
|
+
return c.json({ success: true })
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const err = error as Error
|
|
126
|
+
// Handle validation errors
|
|
127
|
+
if (err.message.includes('Unauthorized')) {
|
|
128
|
+
return c.json({ error: 'Unauthorized' }, 401)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle other errors - log to monitoring system instead of console
|
|
132
|
+
return c.json({ error: 'Internal server error' }, 500)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Route definitions for OpenAPI documentation
|
|
137
|
+
export const getTokensRoute = createRoute({
|
|
138
|
+
method: 'get',
|
|
139
|
+
path: '/-/tokens',
|
|
140
|
+
tags: ['Authentication'],
|
|
141
|
+
summary: 'List Tokens',
|
|
142
|
+
description: `List all authentication tokens for the authenticated user
|
|
143
|
+
\`\`\`bash
|
|
144
|
+
$ npm token list
|
|
145
|
+
\`\`\``,
|
|
146
|
+
request: {},
|
|
147
|
+
responses: {
|
|
148
|
+
200: {
|
|
149
|
+
content: {
|
|
150
|
+
'application/json': {
|
|
151
|
+
schema: z.array(
|
|
152
|
+
z.object({
|
|
153
|
+
token: z.string(),
|
|
154
|
+
key: z.string(),
|
|
155
|
+
cidr_whitelist: z.array(z.string()).optional(),
|
|
156
|
+
readonly: z.boolean(),
|
|
157
|
+
automation: z.boolean(),
|
|
158
|
+
created: z.string(),
|
|
159
|
+
updated: z.string(),
|
|
160
|
+
}),
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
description: 'List of tokens',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
export const createTokenRoute = createRoute({
|
|
170
|
+
method: 'post',
|
|
171
|
+
path: '/-/tokens',
|
|
172
|
+
tags: ['Authentication'],
|
|
173
|
+
summary: 'Create Token',
|
|
174
|
+
description: `Create a new authentication token
|
|
175
|
+
\`\`\`bash
|
|
176
|
+
$ npm token create
|
|
177
|
+
\`\`\``,
|
|
178
|
+
request: {
|
|
179
|
+
body: {
|
|
180
|
+
content: {
|
|
181
|
+
'application/json': {
|
|
182
|
+
schema: z.object({
|
|
183
|
+
token: z.string(),
|
|
184
|
+
uuid: z.string(),
|
|
185
|
+
scope: z.string(),
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
responses: {
|
|
192
|
+
201: {
|
|
193
|
+
content: {
|
|
194
|
+
'application/json': {
|
|
195
|
+
schema: z.object({
|
|
196
|
+
token: z.string(),
|
|
197
|
+
key: z.string(),
|
|
198
|
+
}),
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
description: 'Token created successfully',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
export const updateTokenRoute = createRoute({
|
|
207
|
+
method: 'put',
|
|
208
|
+
path: '/-/tokens',
|
|
209
|
+
tags: ['Authentication'],
|
|
210
|
+
summary: 'Update Token',
|
|
211
|
+
description: `Update an existing authentication token`,
|
|
212
|
+
request: {
|
|
213
|
+
body: {
|
|
214
|
+
content: {
|
|
215
|
+
'application/json': {
|
|
216
|
+
schema: z.object({
|
|
217
|
+
token: z.string(),
|
|
218
|
+
uuid: z.string(),
|
|
219
|
+
scope: z.string(),
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
responses: {
|
|
226
|
+
200: {
|
|
227
|
+
content: {
|
|
228
|
+
'application/json': {
|
|
229
|
+
schema: z.object({
|
|
230
|
+
token: z.string(),
|
|
231
|
+
key: z.string(),
|
|
232
|
+
}),
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
description: 'Token updated successfully',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
export const deleteTokenRoute = createRoute({
|
|
241
|
+
method: 'delete',
|
|
242
|
+
path: '/-/tokens/token/{token}',
|
|
243
|
+
tags: ['Authentication'],
|
|
244
|
+
summary: 'Delete Token',
|
|
245
|
+
description: `Delete an authentication token
|
|
246
|
+
\`\`\`bash
|
|
247
|
+
$ npm token revoke <token>
|
|
248
|
+
\`\`\``,
|
|
249
|
+
request: {
|
|
250
|
+
params: z.object({
|
|
251
|
+
token: z.string(),
|
|
252
|
+
}),
|
|
253
|
+
},
|
|
254
|
+
responses: {
|
|
255
|
+
204: {
|
|
256
|
+
description: 'Token deleted successfully',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getAuthedUser } from '../utils/auth.ts'
|
|
2
|
+
import type { HonoContext } from '../../types.ts'
|
|
3
|
+
import { createRoute, z } from '@hono/zod-openapi'
|
|
4
|
+
|
|
5
|
+
export async function getUsername(c: HonoContext) {
|
|
6
|
+
const user = await getAuthedUser({ c })
|
|
7
|
+
const uuid = user?.uuid || 'anonymous'
|
|
8
|
+
return c.json({ username: uuid }, 200)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getUserProfile(c: HonoContext) {
|
|
12
|
+
const user = await getAuthedUser({ c })
|
|
13
|
+
const uuid = user?.uuid || 'anonymous'
|
|
14
|
+
return c.json({ name: uuid }, 200)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Route definitions for OpenAPI documentation
|
|
18
|
+
export const userProfileRoute = createRoute({
|
|
19
|
+
method: 'get',
|
|
20
|
+
path: '/-/user',
|
|
21
|
+
tags: ['Authentication'],
|
|
22
|
+
summary: 'Get User Profile',
|
|
23
|
+
description: `Returns profile object associated with auth token
|
|
24
|
+
\`\`\`bash
|
|
25
|
+
$ npm profile
|
|
26
|
+
name: johnsmith
|
|
27
|
+
created: 2015-02-26T01:26:01.124Z
|
|
28
|
+
updated: 2023-01-10T21:55:32.118Z
|
|
29
|
+
\`\`\``,
|
|
30
|
+
request: {},
|
|
31
|
+
responses: {
|
|
32
|
+
200: {
|
|
33
|
+
content: {
|
|
34
|
+
'application/json': {
|
|
35
|
+
schema: z.object({
|
|
36
|
+
name: z.string(),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
description: 'User Profile',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const whoamiRoute = createRoute({
|
|
46
|
+
method: 'get',
|
|
47
|
+
path: '/-/whoami',
|
|
48
|
+
tags: ['Authentication'],
|
|
49
|
+
summary: 'Who Am I',
|
|
50
|
+
description: `Returns the username of the authenticated user
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
$ npm whoami
|
|
53
|
+
johnsmith
|
|
54
|
+
\`\`\``,
|
|
55
|
+
request: {},
|
|
56
|
+
responses: {
|
|
57
|
+
200: {
|
|
58
|
+
content: {
|
|
59
|
+
'application/json': {
|
|
60
|
+
schema: z.object({
|
|
61
|
+
username: z.string(),
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
description: 'Username',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|