javascript-solid-server 0.0.97 → 0.0.98
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/package.json +1 -1
- package/src/ap/index.js +1 -1
- package/src/ap/routes/mastodon.js +1 -1
- package/src/ap/routes/oauth.js +38 -11
- package/src/remotestorage.js +2 -1
package/package.json
CHANGED
package/src/ap/index.js
CHANGED
|
@@ -148,7 +148,7 @@ export async function activityPubPlugin(fastify, options = {}) {
|
|
|
148
148
|
version: '2.1',
|
|
149
149
|
software: {
|
|
150
150
|
name: 'jss',
|
|
151
|
-
version: '0.0.
|
|
151
|
+
version: '0.0.98',
|
|
152
152
|
repository: 'https://github.com/JavaScriptSolidServer/JavaScriptSolidServer'
|
|
153
153
|
},
|
|
154
154
|
protocols: ['activitypub', 'solid'],
|
|
@@ -115,7 +115,7 @@ export function createInstanceHandler (config) {
|
|
|
115
115
|
title: config.displayName || 'JSS',
|
|
116
116
|
description: 'SAND Stack: Solid + ActivityPub + Nostr + DID',
|
|
117
117
|
short_description: 'Solid pod with Mastodon-compatible API',
|
|
118
|
-
version: '4.0.0 (compatible; JSS 0.0.
|
|
118
|
+
version: '4.0.0 (compatible; JSS 0.0.98)',
|
|
119
119
|
urls: {
|
|
120
120
|
streaming_api: `${wsProtocol}://${host}`
|
|
121
121
|
},
|
package/src/ap/routes/oauth.js
CHANGED
|
@@ -46,12 +46,19 @@ function parseBody (request) {
|
|
|
46
46
|
* Validate client_id and redirect_uri against registered client
|
|
47
47
|
* Returns { client, error } — client is null if validation fails
|
|
48
48
|
*/
|
|
49
|
-
function validateClient (clientId, redirectUri) {
|
|
49
|
+
function validateClient (clientId, redirectUri, responseType) {
|
|
50
50
|
if (!clientId || !redirectUri) {
|
|
51
51
|
return { client: null, error: 'Missing client_id or redirect_uri' }
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const client = getClient(clientId)
|
|
55
|
+
|
|
56
|
+
// Implicit flow (response_type=token) allows unregistered clients
|
|
57
|
+
// remoteStorage clients pass their origin URL as client_id without pre-registration
|
|
58
|
+
if (!client && responseType === 'token') {
|
|
59
|
+
return { client: { name: clientId, redirect_uri: redirectUri }, error: null }
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
if (!client) {
|
|
56
63
|
return { client: null, error: 'Unknown client_id. Register via POST /api/v1/apps first.' }
|
|
57
64
|
}
|
|
@@ -71,17 +78,17 @@ export function createAuthorizeHandler () {
|
|
|
71
78
|
return async (request, reply) => {
|
|
72
79
|
const { client_id, redirect_uri, response_type, scope, state } = request.query
|
|
73
80
|
|
|
74
|
-
if (response_type && response_type !== 'code') {
|
|
75
|
-
return reply.code(400).send({ error: 'unsupported_response_type', error_description: '
|
|
81
|
+
if (response_type && response_type !== 'code' && response_type !== 'token') {
|
|
82
|
+
return reply.code(400).send({ error: 'unsupported_response_type', error_description: 'Supported: code, token' })
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
const { client, error } = validateClient(client_id, redirect_uri)
|
|
85
|
+
const { client, error } = validateClient(client_id, redirect_uri, response_type)
|
|
79
86
|
if (!client) {
|
|
80
87
|
return reply.code(400).send({ error: 'invalid_client', error_description: error })
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
return reply.type('text/html').send(
|
|
84
|
-
loginPage({ clientId: client_id, redirectUri: redirect_uri, scope: scope || 'read', state, clientName: client.name })
|
|
91
|
+
loginPage({ clientId: client_id, redirectUri: redirect_uri, responseType: response_type || 'code', scope: scope || 'read', state, clientName: client.name })
|
|
85
92
|
)
|
|
86
93
|
}
|
|
87
94
|
}
|
|
@@ -92,28 +99,47 @@ export function createAuthorizeHandler () {
|
|
|
92
99
|
export function createAuthorizePostHandler () {
|
|
93
100
|
return async (request, reply) => {
|
|
94
101
|
const body = parseBody(request)
|
|
95
|
-
const { username, password, client_id, redirect_uri, scope, state } = body
|
|
102
|
+
const { username, password, client_id, redirect_uri, response_type, scope, state } = body
|
|
96
103
|
|
|
97
104
|
// Validate client + redirect_uri (prevent open redirect via form tampering)
|
|
98
|
-
const { client, error: clientError } = validateClient(client_id, redirect_uri)
|
|
105
|
+
const { client, error: clientError } = validateClient(client_id, redirect_uri, response_type)
|
|
99
106
|
if (!client) {
|
|
100
107
|
return reply.code(400).send({ error: 'invalid_client', error_description: clientError })
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
if (!username || !password) {
|
|
104
111
|
return reply.type('text/html').send(
|
|
105
|
-
loginPage({ clientId: client_id, redirectUri: redirect_uri, scope, state, clientName: client.name, error: 'Username and password are required' })
|
|
112
|
+
loginPage({ clientId: client_id, redirectUri: redirect_uri, responseType: response_type || 'code', scope, state, clientName: client.name, error: 'Username and password are required' })
|
|
106
113
|
)
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
const account = await authenticate(username, password)
|
|
110
117
|
if (!account) {
|
|
111
118
|
return reply.type('text/html').send(
|
|
112
|
-
loginPage({ clientId: client_id, redirectUri: redirect_uri, scope, state, clientName: client.name, error: 'Invalid username or password' })
|
|
119
|
+
loginPage({ clientId: client_id, redirectUri: redirect_uri, responseType: response_type || 'code', scope, state, clientName: client.name, error: 'Invalid username or password' })
|
|
113
120
|
)
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
//
|
|
123
|
+
// Implicit grant (response_type=token) — return token directly in fragment (RFC 6749 §4.2.2)
|
|
124
|
+
// Used by remoteStorage clients
|
|
125
|
+
if (response_type === 'token') {
|
|
126
|
+
const accessToken = createToken(account.webId)
|
|
127
|
+
|
|
128
|
+
// Handle OOB — display token
|
|
129
|
+
if (redirect_uri === OOB_REDIRECT) {
|
|
130
|
+
return reply.type('text/html').send(oobPage(accessToken))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fragment-based redirect (token MUST be in fragment, not query — RFC 6749 §4.2.2)
|
|
134
|
+
const params = new URLSearchParams()
|
|
135
|
+
params.set('access_token', accessToken)
|
|
136
|
+
params.set('token_type', 'bearer')
|
|
137
|
+
params.set('scope', scope || 'read')
|
|
138
|
+
if (state) params.set('state', state)
|
|
139
|
+
return reply.redirect(`${redirect_uri}#${params.toString()}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Authorization code grant (response_type=code) — generate one-time auth code (10 min TTL)
|
|
117
143
|
const code = crypto.randomUUID()
|
|
118
144
|
authCodes.set(code, {
|
|
119
145
|
clientId: client_id,
|
|
@@ -196,7 +222,7 @@ export function createTokenHandler () {
|
|
|
196
222
|
/**
|
|
197
223
|
* Minimal login page HTML
|
|
198
224
|
*/
|
|
199
|
-
function loginPage ({ clientId, redirectUri, scope, state, clientName, error }) {
|
|
225
|
+
function loginPage ({ clientId, redirectUri, responseType, scope, state, clientName, error }) {
|
|
200
226
|
const escapedError = error ? escapeHtml(error) : ''
|
|
201
227
|
const escapedName = escapeHtml(clientName || clientId || 'Unknown app')
|
|
202
228
|
|
|
@@ -230,6 +256,7 @@ function loginPage ({ clientId, redirectUri, scope, state, clientName, error })
|
|
|
230
256
|
<form method="POST" action="/oauth/authorize">
|
|
231
257
|
<input type="hidden" name="client_id" value="${escapeHtml(clientId || '')}">
|
|
232
258
|
<input type="hidden" name="redirect_uri" value="${escapeHtml(redirectUri || '')}">
|
|
259
|
+
<input type="hidden" name="response_type" value="${escapeHtml(responseType || 'code')}">
|
|
233
260
|
<input type="hidden" name="scope" value="${escapeHtml(scope || 'read')}">
|
|
234
261
|
${state ? `<input type="hidden" name="state" value="${escapeHtml(state)}">` : ''}
|
|
235
262
|
<label for="username">Username</label>
|
package/src/remotestorage.js
CHANGED
|
@@ -31,7 +31,8 @@ export async function remoteStoragePlugin (fastify, options = {}) {
|
|
|
31
31
|
*/
|
|
32
32
|
function getStoragePath (request) {
|
|
33
33
|
const wildcard = request.params['*'] || ''
|
|
34
|
-
|
|
34
|
+
// Normalize double slashes (RS library appends path to href which ends with /)
|
|
35
|
+
return ('/' + wildcard).replace(/\/\/+/g, '/')
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|