Skip to content
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

🚧 OAuth2 - Authorization Server #2482

Merged
merged 103 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
8e8828b
chore(deps): update zod
matthieusieben Apr 24, 2024
e822a6c
chore(deps): update pino to match entryway version
matthieusieben May 22, 2024
147c98c
chore(tsconfig): remove truncation of types through noErrorTruncation
matthieusieben May 20, 2024
09e5d2a
add support for DPoP token type when logging
matthieusieben Apr 24, 2024
17b0c50
fix(bsky): JSON.parse does not return value of type JSON
matthieusieben Apr 24, 2024
2ca3836
fix(pds): add res property to ReqCtx
matthieusieben Apr 24, 2024
4c49730
fix(pds): properly type getPreferences return value
matthieusieben Apr 24, 2024
3548947
chore(tsconfig): disable noFallthroughCasesInSwitch
matthieusieben Apr 24, 2024
bcc2606
refactor(pds): move tracer config in own file
matthieusieben Apr 24, 2024
3acc8fd
feat(dev-env): start with "pnpm dev"
matthieusieben Apr 24, 2024
3159a10
feat(oauth): add oauth provider & client libs
matthieusieben Apr 24, 2024
1abe6d0
feat(pds): add oauth provider
matthieusieben May 15, 2024
9948fdc
chore: changeset
matthieusieben May 24, 2024
03c3e33
feat: various fixes and improvements
matthieusieben May 27, 2024
286a364
chore(deps): update better-sqlite3 to version 10.0.0 for node 22 comp…
matthieusieben May 31, 2024
3fadcf1
chore(deps): drop unused tslib
matthieusieben Jun 3, 2024
47d6bc0
fix(did): normalize service IDs before looking for duplicates
matthieusieben Jun 3, 2024
ff08414
fix(did): avoid minor type casting
matthieusieben Jun 3, 2024
ec53bbd
fix(did): improve argument validation
matthieusieben Jun 3, 2024
30df230
fix(fetch): explicit use of negation around number comparison
matthieusieben Jun 3, 2024
c20acd8
fix(oauth-provider): improve argument validation
matthieusieben Jun 3, 2024
f06c424
feat(did): add ATPROTO specific "isAtprotoDidWeb" method
matthieusieben Jun 3, 2024
1035f71
feat(rollup-plugin-bundle-manifest): add readme
matthieusieben Jun 3, 2024
c17a1ab
feat(lint): add eqeqeq rule (only allow == and != with null)
matthieusieben Jun 3, 2024
38cf674
fix(oauth-client-browser): typo in gitignore
matthieusieben Jun 3, 2024
4d79f12
fix(oauth-provider): properly name error class file
matthieusieben Jun 3, 2024
42fa5f6
fix(oauth-provider): remove un-necessary useMemo
matthieusieben Jun 3, 2024
dadc431
fix(did-resolver): properly build did:web document url
matthieusieben Jun 3, 2024
73183fb
fix(did-resolver): remove unused types
matthieusieben Jun 3, 2024
137c342
fix(fetch): remove unused utils
matthieusieben Jun 3, 2024
1cef720
fix(pds): remove unused script and dependency
matthieusieben Jun 3, 2024
6fdb8c3
fix(oauth-provider): simplify isSubPath util
matthieusieben Jun 3, 2024
2e88659
fix(oauth-provider): add InvalidRedirectUriError static constructor
matthieusieben Jun 3, 2024
51955be
fix(jwk): improve JWT validation to provide better error messages and…
matthieusieben Jun 3, 2024
e8cca60
fix(pds): use "debug" log level for fetch method
matthieusieben Jun 3, 2024
5b5e754
fix(pds): allow access tokens to contain an unknown "typ" claim (with…
matthieusieben Jun 3, 2024
7639480
fix(jwk): remove un-necessary code
matthieusieben Jun 3, 2024
2876eff
fix(pds): account for whitespace chars when checking JSON
matthieusieben Jun 3, 2024
52bf6e9
fix(pds): remove oauth specific config
matthieusieben Jun 3, 2024
a17f640
fix(pds): run all write queries through transaction or executeWithRetry
matthieusieben Jun 3, 2024
e150d68
fix(oauth-provider:time): simplify constantTime util
matthieusieben Jun 3, 2024
1976673
fix(pds): rename disableSsrf into disableSsrfProtection
matthieusieben Jun 3, 2024
19d8e0c
fix(oauth-client-react-native): remove incomplete package
matthieusieben Jun 3, 2024
655db14
Merge branch 'main' into feat-oauth-server
matthieusieben Jun 3, 2024
8700c56
refactor(pds): remove status & active from ActorAccount
matthieusieben Jun 4, 2024
821d781
fix(pds): invalidate all oauth tokens on takedown
matthieusieben Jun 4, 2024
b9e4091
fix(oauth-provider): enforce token expiry
matthieusieben Jun 4, 2024
b185bfb
fix(pds): properly support deactivated accounts
matthieusieben Jun 4, 2024
a90b9d4
perf(pds:db): allow transaction function to be sync
matthieusieben Jun 4, 2024
0802dcf
refactor(psq:account-manager): expose only query builders & data tran…
matthieusieben Jun 4, 2024
555ee72
fix(oauth-provider): imports from self
matthieusieben Jun 4, 2024
cb244d8
fix(ci): add nested packages to build artifacts
matthieusieben Jun 4, 2024
8ca227b
style(fetch): rename TODO into @TODO
matthieusieben Jun 4, 2024
1a44f8a
style(rollup-plugin-bundle-manifest): remove "TODO" from comment
matthieusieben Jun 4, 2024
a1ba787
style(oauth-client): rename TODO into @TODO
matthieusieben Jun 4, 2024
2f76c2d
style(oauth-provider): rename TODO into @TODO
matthieusieben Jun 4, 2024
6e997de
refactor(oauth-client): remove "OAuth" prefix from types
matthieusieben Jun 4, 2024
32d5988
fix(oauth-client-browser): better type SessionListener
matthieusieben Jun 4, 2024
88bfd51
style(oauth): rename TODO into @TODO
matthieusieben Jun 4, 2024
3d3e86a
fix(oauth-provider): enforce provider max session age
matthieusieben Jun 4, 2024
289ca9c
fix(oauth-provider): check authentication parameters against all clie…
matthieusieben Jun 4, 2024
fd1cf67
fix(api): tests
matthieusieben Jun 4, 2024
90bde69
fix(pds): remove .js from imports for tests
matthieusieben Jun 4, 2024
34b7a50
fix(pds): change account status to match tests
matthieusieben Jun 4, 2024
df01c6d
Merge branch 'main' into feat-oauth-server
matthieusieben Jun 4, 2024
cc7e24b
chore(deps): make all packages depend on the same zod version
matthieusieben Jun 4, 2024
0110293
fix(common-web): remove un-necessary binding of Checkable to "zod"
matthieusieben Jun 4, 2024
b01bd05
refactor(jwk): infer jwt schema from refinement definition
matthieusieben Jun 4, 2024
0c4b14a
Merge remote-tracking branch 'origin/main' into feat-oauth-server
matthieusieben Jun 5, 2024
235cfcf
fix(handle-resolver): allow resolution errors to propagate
matthieusieben Jun 5, 2024
6628b39
fix(did): service endpoint arrays must contain "one or more" element
matthieusieben Jun 5, 2024
28d8084
refactor(pipe): simplify implementation
matthieusieben Jun 5, 2024
976d35a
fix(pds): add missing DB indexes
matthieusieben Jun 5, 2024
b10531a
feat(oauth): Resolve Authorization Server URI through Protected Resou…
matthieusieben Jun 6, 2024
ed717db
style:(oauth-client): import order
matthieusieben Jun 7, 2024
b546453
docs(oauth-provider:redirect-uri): add reference url
matthieusieben Jun 10, 2024
13bfca4
feat(oauth): implement "OAuth Client ID Metadata Document" from draft…
matthieusieben Jun 10, 2024
02430a9
Merge remote-tracking branch 'origin/main' into feat-oauth-server
matthieusieben Jun 10, 2024
aa4d40c
feat(oauth-client): backport changes from feat-oauth-client
matthieusieben Jun 10, 2024
16bfc0f
docs(simple-store): improve comments
matthieusieben Jun 10, 2024
4fde5a3
feat(lexicons): add iterable capabilities
matthieusieben Apr 24, 2024
ea24e2c
fix(pds): type error in dev mode
matthieusieben May 24, 2024
3c5e128
feat(oauth-provider): improved error reporting
matthieusieben Jun 10, 2024
3d0b8a0
fix(oauth-types): allow insecure issuer during tests
matthieusieben Jun 10, 2024
9d24e2b
fix(xrpc-server): allow upload of empty files
matthieusieben Jun 10, 2024
f2c1182
fix: lint
matthieusieben Jun 10, 2024
66ba82a
feat(fetch): keep request reference in errors
matthieusieben Jun 12, 2024
f3e2103
fix(pds): allow more than one session token per user
matthieusieben Jun 12, 2024
7b7fa74
feat(ozone): improve env validation error messages
matthieusieben Jun 12, 2024
50a0e93
fix(oauth-client): account for DPoP when checking for invalid_token e…
matthieusieben Jun 12, 2024
b31d415
fixup! feat(fetch): keep request reference in errors feat(fetch): uti…
matthieusieben Jun 12, 2024
e555003
fixup! feat(fetch): keep request reference in errors feat(fetch): uti…
matthieusieben Jun 12, 2024
af23a54
fix(oauth): various validation fixes
matthieusieben Jun 12, 2024
56ff0ce
Merge branch 'main' into feat-oauth-server
matthieusieben Jun 12, 2024
6f0a72b
Merge branch 'main' into feat-oauth-server
matthieusieben Jun 15, 2024
ec5ce9d
feat(dev-env): fix ozone port number
matthieusieben Jun 15, 2024
598c4d0
fix(fetch-node): prevent fetch against invalid domain names
matthieusieben Jun 15, 2024
73ac072
fix(oauth-provider): add typings for psl dep
matthieusieben Jun 15, 2024
2a8870e
feat(jwk): make type def compatible with TS 4.x
matthieusieben Jun 15, 2024
85e7845
fix(oauth): fixed various spec compliance
matthieusieben Jun 15, 2024
5b7dc29
fix(oauth): lint
matthieusieben Jun 15, 2024
f9c72b2
pds: switch changeset to patch, no breaking changes
devinivy Jun 18, 2024
f3b324f
changeset and config for new oauth deps
devinivy Jun 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 23 additions & 0 deletions .changeset/clever-monkeys-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"@atproto/pds": patch
"@atproto-labs/rollup-plugin-bundle-manifest": minor
"@atproto-labs/handle-resolver-node": minor
"@atproto-labs/simple-store-memory": minor
"@atproto-labs/identity-resolver": minor
"@atproto/oauth-client-browser": minor
"@atproto-labs/handle-resolver": minor
"@atproto-labs/did-resolver": minor
"@atproto-labs/simple-store": minor
"@atproto/oauth-provider": minor
"@atproto-labs/fetch-node": minor
"@atproto/jwk-webcrypto": minor
"@atproto/oauth-client": minor
"@atproto/oauth-types": minor
"@atproto-labs/fetch": minor
"@atproto/jwk-jose": minor
"@atproto-labs/pipe": minor
"@atproto/jwk": minor
"@atproto/did": minor
---

Add OAuth provider capability & support for DPoP signed tokens
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"no-var": "error",
"prefer-const": "warn",
"no-misleading-character-class": "warn",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"@typescript-eslint/no-unused-vars": [
"warn",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/repo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: dist
path: packages/*/dist
path: |
packages/*/dist
packages/*/*/dist
retention-days: 1
test:
name: Test
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ node_modules
lerna-debug.log
npm-debug.log
yarn-error.log
packages/*/dist
packages/**/dist
.idea
packages/*/coverage
.vscode/
test.sqlite
.DS_Store
*.log
tsconfig.build.tsbuildinfo
*.tsbuildinfo
.*.env
.env
\#*\#
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"verify:types": "tsc --build tsconfig.json",
"format": "pnpm lint:fix && pnpm style:fix",
"build": "pnpm --recursive --stream build",
"dev": "pnpm --stream '/^dev:.+$/'",
"dev": "NODE_ENV=development pnpm --stream '/^dev:.+$/'",
"dev:tsc": "tsc --build tsconfig.json --watch",
"dev:pkg": "pnpm --recursive --parallel --stream dev",
"test": "LOG_ENABLED=false ./packages/dev-infra/with-test-redis-and-db.sh pnpm --stream -r test",
Expand Down Expand Up @@ -51,7 +51,9 @@
},
"workspaces": {
"packages": [
"packages/*"
"packages/*",
"packages/oauth/*",
"packages/internal/*"
]
}
}
1 change: 1 addition & 0 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Crypto](./crypto): Atproto's common cryptographic operations.
- [Syntax](./syntax): A library for identifier syntax: NSID, AT URI, handles, etc.
- [Lexicon](./lexicon): A library for validating data using atproto's schema system.
- [OAuth Provider](./oauth/oauth-provider): A library for supporting ATPROTO's OAuth.
- [Repo](./repo): The "atproto repository" core implementation (a Merkle Search Tree).
- [XRPC](./xrpc): An XRPC client implementation.
- [XRPC Server](./xrpc-server): An XRPC server implementation.
Expand Down
2 changes: 1 addition & 1 deletion packages/api/tests/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ describe('agent', () => {

expect(events.length).toEqual(2)
expect(events[0]).toEqual('create-failed')
expect(events[1]).toEqual('network-error')
expect(events[1]).toEqual('expired')
expect(sessions.length).toEqual(2)
expect(typeof sessions[0]).toEqual('undefined')
expect(typeof sessions[1]).toEqual('undefined')
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"multiformats": "^9.9.0",
"p-queue": "^6.6.2",
"pg": "^8.10.0",
"pino": "^8.15.0",
"pino": "^8.21.0",
"pino-http": "^8.2.1",
"sharp": "^0.32.6",
"structured-headers": "^1.0.1",
Expand Down
4 changes: 1 addition & 3 deletions packages/bsky/src/hydration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ export const parseRecordBytes = <T>(
return parseJsonBytes(bytes) as T
}

export const parseJsonBytes = (
bytes: Uint8Array | undefined,
): JSON | undefined => {
export const parseJsonBytes = (bytes: Uint8Array | undefined): unknown => {
if (!bytes || bytes.byteLength === 0) return
const parsed = JSON.parse(ui8.toString(bytes, 'utf8'))
return parsed ?? undefined
Expand Down
119 changes: 81 additions & 38 deletions packages/bsky/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import pino from 'pino'
import { stdSerializers } from 'pino'
import pinoHttp from 'pino-http'
import * as jose from 'jose'
import { subsystemLogger } from '@atproto/common'
import { parseBasicAuth } from './auth-verifier'

export const dbLogger: ReturnType<typeof subsystemLogger> =
subsystemLogger('bsky:db')
Expand All @@ -20,40 +18,85 @@ export const httpLogger: ReturnType<typeof subsystemLogger> =
export const loggerMiddleware = pinoHttp({
logger: httpLogger,
serializers: {
err: (err) => {
return {
code: err?.code,
message: err?.message,
}
},
req: (req) => {
const serialized = pino.stdSerializers.req(req)
const authHeader = serialized.headers.authorization || ''
let auth: string | undefined = undefined
if (authHeader.startsWith('Bearer ')) {
const token = authHeader.slice('Bearer '.length)
const { iss } = jose.decodeJwt(token)
if (iss) {
auth = 'Bearer ' + iss
} else {
auth = 'Bearer Invalid'
}
}
if (authHeader.startsWith('Basic ')) {
const parsed = parseBasicAuth(authHeader)
if (!parsed) {
auth = 'Basic Invalid'
} else {
auth = 'Basic ' + parsed.username
}
}
return {
...serialized,
headers: {
...serialized.headers,
authorization: auth,
},
}
},
err: errSerializer,
req: reqSerializer,
},
})

function errSerializer(err: any) {
return {
code: err?.code,
message: err?.message,
}
}

function reqSerializer(req: any) {
const serialized = stdSerializers.req(req)
serialized.headers = obfuscateHeaders(serialized.headers)
return serialized
}

function obfuscateHeaders(headers: Record<string, string>) {
const obfuscatedHeaders: Record<string, string> = {}
for (const key in headers) {
if (key.toLowerCase() === 'authorization') {
obfuscatedHeaders[key] = obfuscateAuthHeader(headers[key])
} else if (key.toLowerCase() === 'dpop') {
obfuscatedHeaders[key] = obfuscateJws(headers[key]) || 'Invalid'
} else {
obfuscatedHeaders[key] = headers[key]
}
}
return obfuscatedHeaders
}

function obfuscateAuthHeader(authHeader: string): string {
// This is a hot path (runs on every request). Avoid using split() or regex.

const spaceIdx = authHeader.indexOf(' ')
if (spaceIdx === -1) return 'Invalid'

const type = authHeader.slice(0, spaceIdx)
switch (type.toLowerCase()) {
case 'bearer':
return `${type} ${obfuscateBearer(authHeader.slice(spaceIdx + 1))}`
case 'dpop':
return `${type} ${obfuscateJws(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`
case 'basic':
return `${type} ${obfuscateBasic(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`
default:
return `Invalid`
}
}

function obfuscateBasic(token: string): null | string {
if (!token) return null
const buffer = Buffer.from(token, 'base64')
if (!buffer.length) return null // Buffer.from will silently ignore invalid base64 chars
const authHeader = buffer.toString('utf8')
const colIdx = authHeader.indexOf(':')
if (colIdx === -1) return null
const username = authHeader.slice(0, colIdx)
return `${username}:***`
}

function obfuscateBearer(token: string): string {
return obfuscateJws(token) || obfuscateToken(token)
}

function obfuscateToken(token: string): string {
return token ? '***' : ''
}

function obfuscateJws(token: string): null | string {
const firstDot = token.indexOf('.')
if (firstDot === -1) return null

const secondDot = token.indexOf('.', firstDot + 1)
if (secondDot === -1) return null

if (token.indexOf('.', secondDot + 1) !== -1) return null

// Strip the signature
return token.slice(0, secondDot) + '.obfuscated'
}
2 changes: 1 addition & 1 deletion packages/common-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"graphemer": "^1.4.0",
"multiformats": "^9.9.0",
"uint8arrays": "3.0.0",
"zod": "^3.21.4"
"zod": "^3.23.8"
},
"devDependencies": {
"jest": "^28.1.2"
Expand Down
5 changes: 3 additions & 2 deletions packages/common-web/src/check.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ZodError } from 'zod'
// Explicitly not using "zod" types here to avoid mismatching types due to
// version differences.

export interface Checkable<T> {
parse: (obj: unknown) => T
safeParse: (
obj: unknown,
) => { success: true; data: T } | { success: false; error: ZodError }
) => { success: true; data: T } | { success: false; error: Error }
}

export interface Def<T> {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"cbor-x": "^1.5.1",
"iso-datestring-validator": "^2.2.2",
"multiformats": "^9.9.0",
"pino": "^8.15.0"
"pino": "^8.21.0"
},
"devDependencies": {
"jest": "^28.1.2",
Expand Down
3 changes: 2 additions & 1 deletion packages/dev-env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"bin": "dist/bin.js",
"scripts": {
"build": "tsc --build tsconfig.build.json",
"start": "../dev-infra/with-test-redis-and-db.sh node dist/bin.js"
"start": "../dev-infra/with-test-redis-and-db.sh node dist/bin.js",
"dev": "../dev-infra/with-test-redis-and-db.sh node --watch dist/bin.js"
},
"dependencies": {
"@atproto/api": "workspace:^",
Expand Down
1 change: 1 addition & 0 deletions packages/dev-env/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const run = async () => {
},
plc: { port: 2582 },
ozone: {
port: 2587,
chatUrl: 'http://localhost:2590', // must run separate chat service
chatDid: 'did:example:chat',
},
Expand Down
10 changes: 10 additions & 0 deletions packages/dev-env/src/pds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ export class TestPds {
modServiceDid: 'did:example:invalid',
plcRotationKeyK256PrivateKeyHex: plcRotationPriv,
inviteRequired: false,
fetchDisableSsrfProtection: true,
serviceName: 'Development PDS',
primaryColor: '#ffcb1e',
errorColor: undefined,
logoUrl:
'https://uxwing.com/wp-content/themes/uxwing/download/animals-and-birds/bee-icon.png',
homeUrl: 'https://bsky.social/',
termsOfServiceUrl: 'https://bsky.social/about/support/tos',
privacyPolicyUrl: 'https://bsky.social/about/support/privacy-policy',
supportUrl: 'https://blueskyweb.zendesk.com/hc/en-us',
...config,
}
const cfg = pds.envToCfg(env)
Expand Down
36 changes: 36 additions & 0 deletions packages/did/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@atproto/did",
"version": "0.0.1",
"license": "MIT",
"description": "DID resolution and verification library",
"keywords": [
"atproto",
"did",
"validation",
"types"
],
"homepage": "https://atproto.com",
"repository": {
"type": "git",
"url": "https://github.com/bluesky-social/atproto",
"directory": "packages/did"
},
"type": "commonjs",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"dependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"scripts": {
"build": "tsc --build tsconfig.build.json"
}
}
Loading
Loading