Skip to content

Commit

Permalink
feat(oauth): add oauth provider & client libs
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieusieben committed May 15, 2024
1 parent fedbe94 commit fc178c7
Show file tree
Hide file tree
Showing 372 changed files with 29,885 additions and 4,549 deletions.
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
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
11 changes: 11 additions & 0 deletions packages/dev-env/src/pds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export class TestPds {
modServiceDid: 'did:example:invalid',
plcRotationKeyK256PrivateKeyHex: plcRotationPriv,
inviteRequired: false,
fetchDisableSsrf: true,
oauthProviderName: 'PDS (dev)',
oauthProviderPrimaryColor: '#ffcb1e',
oauthProviderLogo:
'https://uxwing.com/wp-content/themes/uxwing/download/animals-and-birds/bee-icon.png',
oauthProviderErrorColor: undefined,
oauthProviderHomeLink: 'https://bsky.social/',
oauthProviderTosLink: 'https://bsky.social/about/support/tos',
oauthProviderPrivacyPolicyLink:
'https://bsky.social/about/support/privacy-policy',
oauthProviderSupportLink: 'https://blueskyweb.zendesk.com/hc/en-us',
...config,
}
const cfg = pds.envToCfg(env)
Expand Down
37 changes: 37 additions & 0 deletions packages/did/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"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": {
"tslib": "^2.6.2",
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"scripts": {
"build": "tsc --build tsconfig.build.json"
}
}
127 changes: 127 additions & 0 deletions packages/did/src/did-document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { z } from 'zod'

import { Did, didSchema } from './did.js'

/**
* RFC3968 compliant URI
*
* @see {@link https://www.rfc-editor.org/rfc/rfc3986}
*/
const rfc3968UriSchema = z.string().refine((data) => {
try {
new URL(data)
return true
} catch {
return false
}
}, 'RFC3968 compliant URI')

const didControllerSchema = z.union([didSchema, z.array(didSchema)])

/**
* @note this schema might be too permissive
*/
const didRelativeUriSchema = z.union([
rfc3968UriSchema,
z.string().regex(/^#[^#]+$/),
])

const didVerificationMethodSchema = z.object({
id: didRelativeUriSchema,
type: z.string().min(1),
controller: didControllerSchema,
publicKeyJwk: z.record(z.string(), z.unknown()).optional(),
publicKeyMultibase: z.string().optional(),
})

/**
* The value of the id property MUST be a URI conforming to [RFC3986]. A
* conforming producer MUST NOT produce multiple service entries with the same
* id. A conforming consumer MUST produce an error if it detects multiple
* service entries with the same id.
*
* @note Normally, only rfc3968UriSchema should be allowed here. However, the
* did:plc uses relative URI. For this reason, we also allow relative URIs
* here.
*/
const didServiceIdSchema = didRelativeUriSchema

/**
* The value of the type property MUST be a string or a set of strings. In order
* to maximize interoperability, the service type and its associated properties
* SHOULD be registered in the DID Specification Registries
* [DID-SPEC-REGISTRIES].
*/
const didServiceTypeSchema = z.union([z.string(), z.array(z.string())])

/**
* The value of the serviceEndpoint property MUST be a string, a map, or a set
* composed of one or more strings and/or maps. All string values MUST be valid
* URIs conforming to [RFC3986] and normalized according to the Normalization
* and Comparison rules in RFC3986 and to any normalization rules in its
* applicable URI scheme specification.
*/
const didServiceEndpointSchema = z.union([
rfc3968UriSchema,
z.record(z.string(), rfc3968UriSchema),
z.array(z.union([rfc3968UriSchema, z.record(z.string(), rfc3968UriSchema)])),
])

/**
* Each service map MUST contain id, type, and serviceEndpoint properties.
* @see {@link https://www.w3.org/TR/did-core/#services}
*/
const didServiceSchema = z.object({
id: didServiceIdSchema,
type: didServiceTypeSchema,
serviceEndpoint: didServiceEndpointSchema,
})

export type DidService = z.infer<typeof didServiceSchema>

const didAuthenticationSchema = z.union([
//
didRelativeUriSchema,
didVerificationMethodSchema,
])

/**
* @note This schema is incomplete
* @see {@link https://www.w3.org/TR/did-core/#production-0}
*/
export const didDocumentSchema = z.object({
'@context': z.union([
z.literal('https://www.w3.org/ns/did/v1'),
z
.array(z.string().url())
.nonempty()
.refine((data) => data[0] === 'https://www.w3.org/ns/did/v1'),
]),
id: didSchema,
controller: didControllerSchema.optional(),
alsoKnownAs: z.array(rfc3968UriSchema).optional(),
service: z.array(didServiceSchema).optional(),
authentication: z.array(didAuthenticationSchema).optional(),
verificationMethod: z
.array(z.union([didVerificationMethodSchema, didRelativeUriSchema]))
.optional(),
})

export type DidDocument<Method extends string = string> = z.infer<
typeof didDocumentSchema
> & { id: Did<Method> }

// TODO: add other refinements ?
export const didDocumentValidator = didDocumentSchema.refinement(
(data) =>
!data.service?.some((s, i, a) => {
for (let j = i + 1; j < a.length; j++) {
if (s.id === a[j]!.id) return true
}
return false
}),
{
code: z.ZodIssueCode.custom,
message: 'Duplicate service id',
},
)
51 changes: 51 additions & 0 deletions packages/did/src/did-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export class DidError extends Error {
constructor(
public readonly did: string,
message: string,
public readonly code: string,
public readonly status = 400,
cause?: unknown,
) {
super(message, { cause })
}

/**
* For compatibility with error handlers in common HTTP frameworks.
*/
get statusCode() {
return this.status
}

override toString() {
return `${this.constructor.name} ${this.code} (${this.did}): ${this.message}`
}

static from(cause: unknown, did: string): DidError {
if (cause instanceof DidError) {
return cause
}

const message =
cause instanceof Error
? cause.message
: typeof cause === 'string'
? cause
: 'An unknown error occurred'

const status =
(typeof (cause as any)?.statusCode === 'number'
? (cause as any).statusCode
: undefined) ??
(typeof (cause as any)?.status === 'number'
? (cause as any).status
: undefined)

return new DidError(did, message, 'did-unknown-error', status, cause)
}
}

export class InvalidDidError extends DidError {
constructor(did: string, message: string, cause?: unknown) {
super(did, message, 'did-invalid', 400, cause)
}
}
Loading

0 comments on commit fc178c7

Please sign in to comment.