Skip to content

Commit

Permalink
Appview v1 ozone auth (#2274)
Browse files Browse the repository at this point in the history
* ozone auth

* build branch

* fix test
  • Loading branch information
dholms authored Mar 6, 2024
1 parent d643b5b commit db5ff13
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-push-bsky-aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
push:
branches:
- main
- appview-v1-courier
- appview-v1-ozone-auth
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
Expand Down
26 changes: 21 additions & 5 deletions packages/bsky/src/auth-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {
AuthRequiredError,
verifyJwt as verifyServiceJwt,
} from '@atproto/xrpc-server'
import { IdResolver } from '@atproto/identity'
import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity'
import * as ui8 from 'uint8arrays'
import express from 'express'
import { getVerificationMaterial } from '@atproto/common'

type ReqCtx = {
req: express.Request
Expand Down Expand Up @@ -151,7 +152,7 @@ export class AuthVerifier {
adminService = async (reqCtx: ReqCtx): Promise<AdminServiceOutput> => {
const { iss, aud } = await this.verifyServiceJwt(reqCtx, {
aud: this.ownDid,
iss: [this.adminDid],
iss: [this.adminDid, `${this.adminDid}#atproto_labeler`],
})
return { credentials: { type: 'admin_service', aud, iss } }
}
Expand Down Expand Up @@ -190,13 +191,28 @@ export class AuthVerifier {
opts: { aud: string | null; iss: string[] | null },
) {
const getSigningKey = async (
did: string,
iss: string,
forceRefresh: boolean,
): Promise<string> => {
if (opts.iss !== null && !opts.iss.includes(did)) {
if (opts.iss !== null && !opts.iss.includes(iss)) {
throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss')
}
return this.idResolver.did.resolveAtprotoKey(did, forceRefresh)
const [did, serviceId] = iss.split('#')
const keyId =
serviceId === 'atproto_labeler' ? 'atproto_label' : 'atproto'
const didDoc = await this.idResolver.did.resolve(did, forceRefresh)
if (!didDoc) {
throw new AuthRequiredError('could not resolve iss did')
}
const parsedKey = getVerificationMaterial(didDoc, keyId)
if (!parsedKey) {
throw new AuthRequiredError('missing or bad key in did doc')
}
const didKey = getDidKeyFromMultibase(parsedKey)
if (!didKey) {
throw new AuthRequiredError('missing or bad key in did doc')
}
return didKey
}

const jwtStr = bearerTokenFromReq(reqCtx.req)
Expand Down
26 changes: 21 additions & 5 deletions packages/bsky/tests/admin/admin-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,31 @@ describe('admin auth', () => {
bskyDid = network.bsky.ctx.cfg.serverDid

modServiceKey = await Secp256k1Keypair.create()
const origResolve = network.bsky.ctx.idResolver.did.resolveAtprotoKey
network.bsky.ctx.idResolver.did.resolveAtprotoKey = async (

const origResolve = network.bsky.ctx.idResolver.did.resolve
network.bsky.ctx.idResolver.did.resolve = async function (
did: string,
forceRefresh?: boolean,
) => {
) {
if (did === modServiceDid || did === altModDid) {
return modServiceKey.did()
return {
'@context': [
'https://www.w3.org/ns/did/v1',
'https://w3id.org/security/multikey/v1',
'https://w3id.org/security/suites/secp256k1-2019/v1',
],
id: did,
verificationMethod: [
{
id: `${did}#atproto`,
type: 'Multikey',
controller: did,
publicKeyMultibase: modServiceKey.did().replace('did:key:', ''),
},
],
}
}
return origResolve(did, forceRefresh)
return origResolve.call(this, did, forceRefresh)
}

agent = network.bsky.getClient()
Expand Down
22 changes: 22 additions & 0 deletions packages/common-web/src/did-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ export const getSigningKey = (
publicKeyMultibase: found.publicKeyMultibase,
}
}

export const getVerificationMaterial = (
doc: DidDocument,
keyId: string,
): { type: string; publicKeyMultibase: string } | undefined => {
const did = getDid(doc)
let keys = doc.verificationMethod
if (!keys) return undefined
if (typeof keys !== 'object') return undefined
if (!Array.isArray(keys)) {
keys = [keys]
}
const found = keys.find(
(key) => key.id === `#${keyId}` || key.id === `${did}#${keyId}`,
)
if (!found?.publicKeyMultibase) return undefined
return {
type: found.type,
publicKeyMultibase: found.publicKeyMultibase,
}
}

export const getSigningDidKey = (doc: DidDocument): string | undefined => {
const parsed = getSigningKey(doc)
if (!parsed) return
Expand Down
17 changes: 17 additions & 0 deletions packages/identity/src/did/atproto-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ export const getKey = (doc: DidDocument): string | undefined => {
return didKey
}

export const getDidKeyFromMultibase = (key: {
type: string
publicKeyMultibase: string
}): string | undefined => {
const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
let didKey: string | undefined = undefined
if (key.type === 'EcdsaSecp256r1VerificationKey2019') {
didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
} else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {
didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
} else if (key.type === 'Multikey') {
const parsed = crypto.parseMultikey(key.publicKeyMultibase)
didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)
}
return didKey
}

export const parseToAtprotoDocument = (
doc: DidDocument,
): Partial<AtprotoData> => {
Expand Down

0 comments on commit db5ff13

Please sign in to comment.