Replies: 2 comments 2 replies
-
Just for some extra inspiration, here's how I've been doing it. Altho, I like your API as well (better?), and it probably inspires fewer gotchas because it forces an audience, isRevoked & capability check. // a check is a fn that takes a ucan & returns an Error or null if it's all good
type Check = (ucan: ucan.Chained) => Error | null
// parses a request, makes sure there's a ucan in the bearer token, parses it to a ucan then runs `checkUcan`
export const checkReq = async (
req: Request,
...checks: Check[]
): Promise<ucan.Store> => {
const header = req.headers.authorization
if (!header) {
throw new ServerError(403, 'No UCAN found in message headers')
}
let decoded: ucan.Chained
try {
const stripped = header.replace('Bearer ', '')
decoded = await ucan.Chained.fromToken(stripped)
} catch (err) {
throw new ServerError(
403,
`Could not parse a proper UCAN from req header: ${err}`,
)
}
let token: ucan.Chained
try {
token = await auth.checkUcan(decoded, ...checks)
} catch (err) {
throw new ServerError(
403,
`Attached UCAN does not allow the requested operation: ${err}`,
)
}
return ucan.Store.fromTokens([token.encoded()])
}
// runs some checks on UCAN & throws any errors it encounters
export const checkUcan = async (
token: ucan.Chained,
...checks: Check[]
): Promise<Chained> => {
for (let i = 0; i < checks.length; i++) {
const maybeErr = checks[i](token)
if (maybeErr !== null) {
throw maybeErr
}
}
return token
}
// some example checks
export const isRoot =
() =>
(token: Chained): Error | null => {
if (token.proofs && token.proofs.length > 0) {
throw new Error('Ucan is an attenuation and not the root')
}
return null
}
export const hasAudience =
(did: string) =>
(token: Chained): Error | null => {
if (token.audience() !== did) {
return new Error('Ucan audience does not match server Did')
}
return null
}
export const hasValidCapability =
(rootDid: string, needed: BlueskyCapability) =>
(token: Chained): Error | null => {
// ......
}
export const hasPostingPermission =
(did: string, namespace: string, collection: Collection, tid: TID) =>
(token: Chained): Error | null => {
const needed = writeCap(did, namespace, collection, tid)
return hasValidCapability(did, needed)(token)
}
// And in action in a server route!
// (note this just throws formatted server errors that get caught by the middleware
const ucanStore = await auth.checkReq(
req,
ucanCheck.hasAudience(SERVER_DID),
ucanCheck.hasPostingPermission(
post.author,
post.namespace,
'posts',
post.tid,
),
) |
Beta Was this translation helpful? Give feedback.
1 reply
-
Few questions:
|
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This is somewhat WIP. I'm sketching out an API that services can use for checking UCANs.
Today, services would need some combination of
Chained.fromToken
,hasCapability
and manual checking of theChained#audience
on the outer level.I think we can create a single thing that developers should be nudged towards when they check UCANs that makes sure they've got everything covered they need to cover.
The interface I'm thinking of could look like this:
And would be used like this for example:
Beta Was this translation helpful? Give feedback.
All reactions