-
Notifications
You must be signed in to change notification settings - Fork 578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ozone ACLs #2252
Ozone ACLs #2252
Changes from 9 commits
6a26396
125f721
36da1d9
32b3de8
199b754
d1d39ff
cb53fdc
e3bfb17
a642063
f7ef546
0482a92
5df31de
dd891d4
2ca4fee
6ba5f6c
9b2500e
c76fd03
87f00f2
8341c7a
11b7af2
037f163
fc1c40d
5e1c5fd
82acea2
4c7db5c
81f9d69
7be8445
592518c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,28 +45,28 @@ type RoleOutput = { | |
} | ||
} | ||
|
||
type AdminServiceOutput = { | ||
type ModServiceOutput = { | ||
credentials: { | ||
type: 'admin_service' | ||
type: 'mod_service' | ||
aud: string | ||
iss: string | ||
} | ||
} | ||
|
||
export type AuthVerifierOpts = { | ||
ownDid: string | ||
adminDid: string | ||
modServiceDid: string | ||
adminPasses: string[] | ||
} | ||
|
||
export class AuthVerifier { | ||
public ownDid: string | ||
public adminDid: string | ||
public modServiceDid: string | ||
private adminPasses: Set<string> | ||
|
||
constructor(public dataplane: DataPlaneClient, opts: AuthVerifierOpts) { | ||
this.ownDid = opts.ownDid | ||
this.adminDid = opts.adminDid | ||
this.modServiceDid = opts.modServiceDid | ||
this.adminPasses = new Set(opts.adminPasses) | ||
} | ||
|
||
|
@@ -83,13 +83,21 @@ export class AuthVerifier { | |
if (!this.parseRoleCreds(ctx.req).admin) { | ||
throw new AuthRequiredError('bad credentials') | ||
} | ||
return { credentials: { type: 'standard', iss, aud } } | ||
return { | ||
credentials: { type: 'standard', iss, aud }, | ||
} | ||
} | ||
const { iss, aud } = await this.verifyServiceJwt(ctx, { | ||
aud: this.ownDid, | ||
iss: null, | ||
}) | ||
return { credentials: { type: 'standard', iss, aud } } | ||
return { | ||
credentials: { | ||
type: 'standard', | ||
iss, | ||
aud, | ||
}, | ||
} | ||
} | ||
|
||
standardOptional = async ( | ||
|
@@ -159,19 +167,19 @@ export class AuthVerifier { | |
} | ||
} | ||
|
||
adminService = async (reqCtx: ReqCtx): Promise<AdminServiceOutput> => { | ||
modService = async (reqCtx: ReqCtx): Promise<ModServiceOutput> => { | ||
const { iss, aud } = await this.verifyServiceJwt(reqCtx, { | ||
aud: this.ownDid, | ||
iss: [this.adminDid], | ||
iss: [this.modServiceDid, `${this.modServiceDid}#atproto_mod`], | ||
}) | ||
return { credentials: { type: 'admin_service', aud, iss } } | ||
return { credentials: { type: 'mod_service', aud, iss } } | ||
} | ||
|
||
roleOrAdminService = async ( | ||
roleOrModService = async ( | ||
reqCtx: ReqCtx, | ||
): Promise<RoleOutput | AdminServiceOutput> => { | ||
): Promise<RoleOutput | ModServiceOutput> => { | ||
if (isBearerToken(reqCtx.req)) { | ||
return this.adminService(reqCtx) | ||
return this.modService(reqCtx) | ||
} else { | ||
return this.role(reqCtx) | ||
} | ||
|
@@ -195,12 +203,13 @@ export class AuthVerifier { | |
opts: { aud: string | null; iss: string[] | null }, | ||
) { | ||
const getSigningKey = async ( | ||
did: string, | ||
iss: string, | ||
_forceRefresh: boolean, // @TODO consider propagating to dataplane | ||
): Promise<string> => { | ||
if (opts.iss !== null && !opts.iss.includes(did)) { | ||
if (opts.iss !== null && !opts.iss.includes(iss)) { | ||
throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') | ||
} | ||
const [did, keyId = 'atproto'] = iss.split('#') | ||
let identity: GetIdentityByDidResponse | ||
try { | ||
identity = await this.dataplane.getIdentityByDid({ did }) | ||
|
@@ -211,7 +220,7 @@ export class AuthVerifier { | |
throw err | ||
} | ||
const keys = unpackIdentityKeys(identity.keys) | ||
const didKey = getKeyAsDidKey(keys, { id: 'atproto' }) | ||
const didKey = getKeyAsDidKey(keys, { id: keyId }) | ||
if (!didKey) { | ||
throw new AuthRequiredError('missing or bad key') | ||
} | ||
|
@@ -226,6 +235,12 @@ export class AuthVerifier { | |
return { iss: payload.iss, aud: payload.aud } | ||
} | ||
|
||
isModService(iss: string): boolean { | ||
return [this.modServiceDid, `${this.modServiceDid}#atproto_mod`].includes( | ||
iss, | ||
) | ||
} | ||
|
||
nullCreds(): NullOutput { | ||
return { | ||
credentials: { | ||
|
@@ -236,16 +251,19 @@ export class AuthVerifier { | |
} | ||
|
||
parseCreds( | ||
creds: StandardOutput | RoleOutput | AdminServiceOutput | NullOutput, | ||
creds: StandardOutput | RoleOutput | ModServiceOutput | NullOutput, | ||
) { | ||
const viewer = | ||
creds.credentials.type === 'standard' ? creds.credentials.iss : null | ||
const canViewTakedowns = | ||
(creds.credentials.type === 'role' && creds.credentials.admin) || | ||
creds.credentials.type === 'admin_service' | ||
creds.credentials.type === 'mod_service' || | ||
(creds.credentials.type === 'standard' && | ||
this.isModService(creds.credentials.iss)) | ||
Comment on lines
+263
to
+265
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Making sure I understand correctly—this means the mod service can view takedowns either:
Does that sound right? If so, when does the latter come into play? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup that does sound right - maybe it could be communicated better. But the latter comes into play on some of the view routes where we still want to show taken down content - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could use a separate authVerifier there like |
||
const canPerformTakedown = | ||
(creds.credentials.type === 'role' && creds.credentials.admin) || | ||
creds.credentials.type === 'admin_service' | ||
creds.credentials.type === 'mod_service' | ||
|
||
return { | ||
viewer, | ||
canViewTakedowns, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
#atproto_mod
is the id of the key. Do we want to use the id of the service here, map it to the id of the key when verifying?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup we chatted about this - I changed this to be the serviceId to keep it inline with possible future updates 👍