Skip to content

Commit

Permalink
Implement possibility to use custom webId
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkvon committed Nov 1, 2024
1 parent 9d66780 commit b69bed6
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 19 deletions.
19 changes: 12 additions & 7 deletions packages/core/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ export interface Endpoint {
defaultContentType: string
}

export const getEndpoints = (webId: string): Endpoint[] => {
export const getEndpoints = (webId: string, baseUrl?: string): Endpoint[] => {
const { pathname, hash, origin } = new URL(webId)

return [
const endpoints: Endpoint[] = [
{
method: 'get',
path: '/.well-known/openid-configuration',
body: {
'application/json': {
issuer: origin,
jwks_uri: new URL('/jwks', webId).toString(),
issuer: baseUrl ?? origin,
jwks_uri: new URL('/jwks', baseUrl ?? origin).toString(),
response_types_supported: ['id_token', 'token'],
scopes_supported: ['openid', 'webid'],
},
Expand All @@ -30,7 +30,10 @@ export const getEndpoints = (webId: string): Endpoint[] => {
body: { 'application/json': { keys: [fullJwkPublicKey] } },
defaultContentType: 'application/json',
},
{
]

if (!baseUrl) {
endpoints.push({
method: 'get',
path: pathname,
body: {
Expand All @@ -46,6 +49,8 @@ export const getEndpoints = (webId: string): Endpoint[] => {
},
// TODO add application/ld+json
defaultContentType: 'text/turtle',
},
]
})
}

return endpoints
}
5 changes: 4 additions & 1 deletion packages/core/src/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ export const fullJwkPublicKey = {

export const getAuthenticatedFetch = async (
webId: string,
baseUrl?: string,
): Promise<typeof globalThis.fetch> => {
const { origin: baseUrl } = new URL(webId)
const { origin } = new URL(webId)

baseUrl ??= origin
const dpopKey = await generateDpopKeyPair()

const jkt = await calculateJwkThumbprint(
Expand Down
1 change: 1 addition & 0 deletions packages/koa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@types/koa__router": "^12.0.4",
"css-authn": "^0.0.16",
"koa": "^2.15.3",
"msw": "^2.6.0",
"rdf-namespaces": "^1.12.0",
"typescript": "^5.6.2",
"vitest": "^2.1.4"
Expand Down
4 changes: 2 additions & 2 deletions packages/koa/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import Router from '@koa/router'
import { getEndpoints } from '@soid/core'
export { getAuthenticatedFetch } from '@soid/core'

export const solidIdentity = (identity: string) => {
export const solidIdentity = (identity: string, baseUrl?: string) => {
const router = new Router()

const endpoints = getEndpoints(identity)
const endpoints = getEndpoints(identity, baseUrl)

for (const endpoint of endpoints) {
router[endpoint.method](endpoint.path, async ctx => {
Expand Down
118 changes: 115 additions & 3 deletions packages/koa/src/tests/identity.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Router from '@koa/router'
import { App, AppRunner, joinFilePath } from '@solid/community-server'
import Koa from 'koa'
import * as msw from 'msw'
import * as mswNode from 'msw/node'
import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
import { getAuthenticatedFetch, solidIdentity } from '../index.js'
import { getDefaultPerson } from './helpers/index.js'
Expand All @@ -19,7 +21,7 @@ beforeAll(async () => {
css = await new AppRunner().create({
shorthand: {
port: 4000,
loggingLevel: 'off',
// loggingLevel: 'off',
seedConfig: joinFilePath(__dirname, './css-pod-seed.json'), // set up some Solid accounts
},
})
Expand Down Expand Up @@ -57,11 +59,40 @@ const stopServer = (server: Awaited<ReturnType<typeof startServer>>) =>
server.close(err => (err ? reject(err) : resolve())),
)

const setupIdentityServer = ({
webId,
provider,
}: {
webId: string
provider: string
}) => {
const { pathname, origin, hash } = new URL(webId)

const url = new URL(pathname, origin).toString()

return mswNode.setupServer(
msw.http.get(url, () =>
msw.HttpResponse.text(
`@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@base <${new URL(pathname, origin).toString()}>.
<${hash}>
a foaf:Agent;
solid:oidcIssuer <${provider}>.
`,
{ headers: { 'Content-Type': 'text/turtle' } },
),
),
)
}

describe('Solid identity', () => {
test('Default identity endpoints can be set up', async () => {
const webId = 'http://localhost:3000/test/asdf#identity'
const app = setupServer(webId)
const server = await startServer(app, 3000)

// check .well-known endpoint
const response = await fetch(
'http://localhost:3000/.well-known/openid-configuration',
Expand All @@ -81,7 +112,31 @@ describe('Solid identity', () => {

test.todo('default webId can be extended with additional triples')

test.todo('Identity with external webId can be set up')
test('Identity with external webId can be set up', async () => {
const webId = 'http://localhost:5000/test/asdf#identity'
const baseUrl = 'http://localhost:3000'

const identityServer = setupIdentityServer({ webId, provider: baseUrl })
identityServer.listen()
const app = setupServer(webId, baseUrl)
const server = await startServer(app, 3000)
// check .well-known endpoint
const response = await fetch(
'http://localhost:3000/.well-known/openid-configuration',
)
expect(response.ok).toEqual(true)
const body = await response.json()
expect(body).toHaveProperty('jwks_uri')

const jwksResponse = await fetch(body.jwks_uri)
expect(jwksResponse.ok).toEqual(true)

const webIdResponse = await fetch(webId)
expect(webIdResponse.ok).toEqual(true)

await stopServer(server)
identityServer.close()
})

test('Fetch protected data from CSS with default identity', async () => {
// set up the koa service and soid middleware
Expand Down Expand Up @@ -131,5 +186,62 @@ describe('Solid identity', () => {
await stopServer(server)
}, 10000)

test.todo('Fetch protected data from CSS with custom webId identity')
test('Fetch protected data from CSS with custom webId identity', async () => {
// set up the koa service and soid middleware
const serviceWebId = 'http://localhost:5000/test/asdf#identity'
const provider = 'http://localhost:3000'
const app = setupServer(serviceWebId, provider)
const server = await startServer(app, 3000)
const identityServer = setupIdentityServer({
webId: serviceWebId,
provider: 'http://localhost:3000',
})

identityServer.listen()

// setup some data on the server
const person = await getDefaultPerson(
{
email: 'person@example',
password: 'password',
pods: [{ name: 'person' }],
},
'http://localhost:4000',
)

const resourceUrl = new URL('./testfolder/test', person.podUrl).toString()

await createResource({
url: resourceUrl,
body: '<#this> a "test"',
acls: [
{
permissions: ['Read', 'Write', 'Append', 'Control'],
agents: [person.webId],
},
{
permissions: ['Read'],
agents: [serviceWebId],
},
],
authenticatedFetch: person.fetch,
})

// try to fetch data from CSS
// unauthenticated fetch should fail
const plainResponse = await fetch(resourceUrl)
expect(plainResponse.ok).toEqual(false)

// authenticated fetch should succeed
const authenticatedFetch = await getAuthenticatedFetch(
serviceWebId,
provider,
)

const authenticatedResponse = await authenticatedFetch(resourceUrl)
expect(authenticatedResponse.ok).toEqual(true)
// stop the server
await stopServer(server)
identityServer.close()
})
})
Loading

0 comments on commit b69bed6

Please sign in to comment.