-
Notifications
You must be signed in to change notification settings - Fork 600
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a9c13aa
commit 1d7f7f6
Showing
8 changed files
with
322 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
packages/oauth-server/src/authorization/authorization-store.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Client } from '../client/client' | ||
import { DeviceId } from '../device/device-id' | ||
import { AuthorizationRequest } from './types' | ||
|
||
export type RequestId = string & { __RequestId: true } | ||
|
||
export interface AuthorizationStore { | ||
// TODO Validate request agains client metadata !!!! | ||
createAuthorizationRequest( | ||
client: Client, | ||
request: AuthorizationRequest, | ||
deviceId?: DeviceId, | ||
): Promise<RequestId> | ||
|
||
// TODO: Add validation logic !!! | ||
// try { | ||
// // TODO : better error | ||
// if (result.deviceId && result.deviceId !== deviceId) { | ||
// throw new TypeError('Invalid request_uri') | ||
// } | ||
// // TODO : better error | ||
// if (result.clientId !== client.id) { | ||
// throw new TypeError('Invalid request_uri') | ||
// } | ||
// } catch (err) { | ||
// await this.store.deleteAuthorizationRequest(requestId) | ||
// throw err | ||
// } | ||
getAuthorizationRequest( | ||
client: Client, | ||
requestId: RequestId, | ||
deviceId: DeviceId, | ||
): Promise<AuthorizationRequest> | ||
|
||
updateAuthorizationRequest( | ||
requestId: RequestId, | ||
data: { | ||
request?: AuthorizationRequest | ||
deviceId?: DeviceId | ||
}, | ||
): Promise<void> | ||
|
||
deleteAuthorizationRequest(requestId: RequestId): Promise<void> | ||
|
||
listActiveSessions(deviceId: DeviceId): Promise<{ | ||
sessionId: string | ||
deviceId: DeviceId | ||
accountId: string // + Data ? | ||
}> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,23 @@ | ||
import { LRUCache } from 'lru-cache' | ||
|
||
import { DidWeb } from '../util/did-web' | ||
import { Fetch } from '../util/fetch' | ||
import { Client } from './client' | ||
import { ClientStore } from './client-store' | ||
import { | ||
forbiddenDomainNameRequestTransform, | ||
ssrfSafeRequestTransform, | ||
} from '../util/fetch-request' | ||
|
||
import { fetchMaxSizeProcessor } from '../util/fetch-response' | ||
import { Client, ClientConfig } from './client' | ||
import { combine } from '../util/transformer' | ||
|
||
export type ClientRegistryConfig = NonNullable< | ||
Parameters<typeof ClientRegistry.fromConfig>[0] | ||
> | ||
ClientStoreMemory, | ||
ClientStoreMemoryConfig, | ||
} from './client-store-memory' | ||
import { ClientId } from './types' | ||
|
||
export class ClientRegistry { | ||
static fromConfig({ | ||
fetch: fetchFn = global.fetch as Fetch, | ||
cacheTtl = 60 * 60 * 1000, // 1 hour | ||
cacheMaxSize = 50 * 1024 * 1024, // 50MB | ||
ssrfProtection = true, | ||
maxResponseSize = 512 * 1024, // 512kB | ||
forbiddenDomainNames = ['bsky.social', 'bsky.network'], | ||
} = {}) { | ||
const fetch: Fetch = combine( | ||
/** | ||
* Since we will be fetching from the network based on user provided | ||
* input, we need to make sure that the request is not vulnerable to SSRF | ||
* attacks. | ||
*/ | ||
ssrfSafeRequestTransform(() => !ssrfProtection), | ||
/** | ||
* Disallow fetching from domains we know are not atproto client | ||
* implementation. | ||
*/ | ||
forbiddenDomainNameRequestTransform(forbiddenDomainNames), | ||
|
||
// Wrap the fetch function to add some extra features | ||
fetchFn, | ||
|
||
/** | ||
* Since we will be fetching user owned data, we need to make sure that | ||
* an attacker cannot force us to download a large amounts of data. | ||
*/ | ||
fetchMaxSizeProcessor(maxResponseSize), | ||
) | ||
|
||
const cache = new LRUCache<DidWeb, Client, ClientRegistry>({ | ||
ttl: cacheTtl, | ||
maxSize: cacheMaxSize, | ||
sizeCalculation: (client) => client.memoryUsage, | ||
updateAgeOnGet: false, | ||
updateAgeOnHas: false, | ||
allowStaleOnFetchRejection: true, | ||
ignoreFetchAbort: true, | ||
fetchMethod: async (clientId, _, opts) => opts.context.load(clientId), | ||
}) | ||
|
||
return new ClientRegistry(fetch, cache) | ||
static memory(config?: ClientStoreMemoryConfig) { | ||
const store = new ClientStoreMemory(config) | ||
return new ClientRegistry(store) | ||
} | ||
|
||
constructor( | ||
readonly fetch: Fetch, | ||
readonly cache: LRUCache<DidWeb, Client, ClientRegistry>, | ||
) {} | ||
|
||
public async get(clientId: DidWeb): Promise<Client> { | ||
// Since loopback clients will not cause any traffic, we won't cache them. | ||
// This will allow to reserve the cache for clients that require HTTP | ||
// requests to be fetched. | ||
if (!Client.isLoopbackClient(clientId)) { | ||
const cached = await this.cache.fetch(clientId, { context: this }) | ||
if (cached != null) return cached | ||
} | ||
|
||
return this.load(clientId) | ||
} | ||
constructor(protected readonly store: ClientStore) {} | ||
|
||
public async load(clientId: DidWeb): Promise<Client> { | ||
return Client.fromId(clientId, { fetch: this.fetch }) | ||
public async fetch(clientId: ClientId): Promise<Client> { | ||
// We don't want to store loopback clients | ||
return Client.isLoopbackClientId(clientId) | ||
? Client.forLoopback(clientId) | ||
: this.store.fetch(clientId) | ||
} | ||
} |
Oops, something went wrong.