-
Notifications
You must be signed in to change notification settings - Fork 582
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pds): create oauth-manager shell
- Loading branch information
1 parent
0d1b0a8
commit 4432e6b
Showing
13 changed files
with
336 additions
and
1 deletion.
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
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
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 |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Keyset } from '@atproto/jwk' | ||
import { OAuthProvider } from '@atproto/oauth-provider' | ||
import { Redis } from 'ioredis' | ||
|
||
import { AccountManager } from './../account-manager/index' | ||
|
||
import { OAuthAccountStore } from './stores/oauth-account-store' | ||
import { OAuthClientStore } from './stores/oauth-client-store' | ||
import { OAuthReplayStoreMemory } from './stores/oauth-replay-store-memory' | ||
import { OAuthReplayStoreRedis } from './stores/oauth-replay-store-redis' | ||
import { OAuthRequestStore } from './stores/oauth-request-store' | ||
import { OAuthSessionStore } from './stores/oauth-session-store' | ||
import { OAuthTokenStore } from './stores/oauth-token-store' | ||
|
||
export class OAuthManager { | ||
private readonly provider: OAuthProvider | ||
|
||
constructor( | ||
issuer: string | URL, | ||
keyset: Keyset, | ||
accountManager: AccountManager, | ||
redis?: Redis, | ||
disableSsrfProtection = false, | ||
) { | ||
this.provider = new OAuthProvider({ | ||
issuer, | ||
keyset, | ||
clientStore: new OAuthClientStore(disableSsrfProtection), | ||
accountStore: new OAuthAccountStore(accountManager), | ||
requestStore: new OAuthRequestStore(accountManager), | ||
replayStore: redis | ||
? new OAuthReplayStoreRedis(redis) | ||
: new OAuthReplayStoreMemory(), | ||
sessionStore: new OAuthSessionStore(accountManager), | ||
tokenStore: new OAuthTokenStore(accountManager), | ||
|
||
onTokenResponse: ({ tokenResponse, account }) => { | ||
// ATPROTO spec extension: add the sub claim to the token response | ||
tokenResponse['sub'] = account.sub | ||
}, | ||
}) | ||
} | ||
|
||
get router() { | ||
return this.provider.httpHandler() | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
packages/pds/src/oauth-provider/stores/oauth-account-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,36 @@ | ||
import type { | ||
Account, | ||
AccountLoginCredentials, | ||
AccountStore, | ||
DeviceAccountInfo, | ||
DeviceId, | ||
Sub, | ||
} from '@atproto/oauth-provider' | ||
import { AccountManager } from '../../account-manager/index.js' | ||
|
||
export class OAuthAccountStore implements AccountStore { | ||
constructor(private readonly accountManager: AccountManager) {} | ||
|
||
async addAccount( | ||
deviceId: DeviceId, | ||
credentials: AccountLoginCredentials, | ||
): Promise<{ account: Account; info: DeviceAccountInfo } | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async listAccounts( | ||
deviceId: DeviceId, | ||
sub?: Sub, | ||
): Promise<{ account: Account; info: DeviceAccountInfo }[]> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async updateAccountInfo( | ||
deviceId: DeviceId, | ||
sub: Sub, | ||
info: Partial<DeviceAccountInfo>, | ||
): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async removeAccount(deviceId: DeviceId, sub: Sub): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/pds/src/oauth-provider/stores/oauth-client-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,18 @@ | ||
import { ClientStore } from '@atproto/oauth-provider' | ||
import { OAuthClientFQDNStore } from '@atproto/oauth-provider-client-fqdn' | ||
import { oauthProviderLogger } from '../../logger' | ||
|
||
export class OAuthClientStore | ||
extends OAuthClientFQDNStore | ||
implements ClientStore | ||
{ | ||
constructor(disableSsrfProtection = false) { | ||
super({ | ||
safeFetch: !disableSsrfProtection, | ||
fetch: async (request) => { | ||
oauthProviderLogger.debug({ uri: request.url }, 'client metadata fetch') | ||
return fetch(request) | ||
}, | ||
}) | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
packages/pds/src/oauth-provider/stores/oauth-replay-store-memory.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,29 @@ | ||
import type { ReplayStore } from '@atproto/oauth-provider' | ||
|
||
export class OAuthReplayStoreMemory implements ReplayStore { | ||
private lastCleanup = Date.now() | ||
private nonces = new Map<string, number>() | ||
|
||
async unique(nonce: string, timeframe: number): Promise<boolean> { | ||
this.cleanup() | ||
|
||
const now = Date.now() | ||
|
||
const expires = this.nonces.get(nonce) | ||
if (expires != null && expires > now) return false | ||
|
||
this.nonces.set(nonce, now + timeframe) | ||
return true | ||
} | ||
|
||
private cleanup() { | ||
const now = Date.now() | ||
|
||
if (this.lastCleanup < now - 60_000) { | ||
for (const [nonce, expires] of this.nonces) { | ||
if (expires < now) this.nonces.delete(nonce) | ||
} | ||
this.lastCleanup = now | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/pds/src/oauth-provider/stores/oauth-replay-store-redis.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,20 @@ | ||
import type { ReplayStore } from '@atproto/oauth-provider' | ||
import { Redis } from 'ioredis' | ||
|
||
export class OAuthReplayStoreRedis implements ReplayStore { | ||
constructor(private readonly redis: Redis) {} | ||
|
||
/** | ||
* Returns true if the nonce is unique within the given timeframe. | ||
*/ | ||
async unique(nonce: string, timeframe: number): Promise<boolean> { | ||
const key = `oauth:nonce:${nonce}` | ||
|
||
if ((await this.redis.exists(key)) && (await this.redis.ttl(key)) > 0) { | ||
return false | ||
} | ||
|
||
await this.redis.set(key, '1', 'EX', Math.ceil(timeframe / 1000)) | ||
return true | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
packages/pds/src/oauth-provider/stores/oauth-request-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,46 @@ | ||
import type { | ||
Account, | ||
AccountLoginCredentials, | ||
AccountStore, | ||
Sub, | ||
Code, | ||
DeviceAccountInfo, | ||
DeviceId, | ||
RefreshToken, | ||
ReplayStore, | ||
RequestData, | ||
RequestId, | ||
RequestStore, | ||
SessionData, | ||
SessionStore, | ||
TokenData, | ||
TokenId, | ||
TokenInfo, | ||
TokenStore, | ||
} from '@atproto/oauth-provider' | ||
import { AccountManager } from '../../account-manager/index.js' | ||
|
||
export class OAuthRequestStore implements RequestStore { | ||
constructor(private readonly accountManager: AccountManager) {} | ||
|
||
async createRequest(id: RequestId, data: RequestData): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async readRequest(id: RequestId): Promise<RequestData | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async updateRequest( | ||
id: RequestId, | ||
data: Partial<RequestData>, | ||
): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async deleteRequest(id: RequestId): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async findRequest( | ||
code: Code, | ||
): Promise<{ id: RequestId; data: RequestData } | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
packages/pds/src/oauth-provider/stores/oauth-session-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,23 @@ | ||
import type { | ||
DeviceId, | ||
SessionData, | ||
SessionStore, | ||
} from '@atproto/oauth-provider' | ||
import { AccountManager } from '../../account-manager/index.js' | ||
|
||
export class OAuthSessionStore implements SessionStore { | ||
constructor(private readonly accountManager: AccountManager) {} | ||
|
||
async createSession(deviceId: DeviceId, data: SessionData): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async readSession(deviceId: DeviceId): Promise<SessionData | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async updateSession( | ||
deviceId: DeviceId, | ||
data: Partial<SessionData>, | ||
): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
packages/pds/src/oauth-provider/stores/oauth-token-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,36 @@ | ||
import type { | ||
Code, | ||
RefreshToken, | ||
TokenData, | ||
TokenId, | ||
TokenInfo, | ||
TokenStore, | ||
} from '@atproto/oauth-provider' | ||
import { AccountManager } from '../../account-manager/index.js' | ||
|
||
export class OAuthTokenStore implements TokenStore { | ||
constructor(private readonly accountManager: AccountManager) {} | ||
|
||
async createToken(tokenId: TokenId, data: TokenData): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async readToken(tokenId: TokenId): Promise<TokenInfo | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async updateToken( | ||
prevTokenId: TokenId, | ||
nextTokenId: TokenId, | ||
data: Partial<TokenData>, | ||
): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async deleteToken(tokenId: TokenId): Promise<void> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async findToken(refreshToken: RefreshToken): Promise<TokenInfo | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
async findTokenByCode(code: Code): Promise<TokenInfo | null> { | ||
throw new Error('Method not implemented.') | ||
} | ||
} |
Oops, something went wrong.