@atproto/oauth-client-expo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE.txt +7 -0
- package/README.md +140 -0
- package/android/.editorconfig +2 -0
- package/android/build.gradle +47 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/atprotooauthclient/Crypto.kt +69 -0
- package/android/src/main/java/expo/modules/atprotooauthclient/ExpoAtprotoOAuthClientModule.kt +41 -0
- package/android/src/main/java/expo/modules/atprotooauthclient/Jose.kt +116 -0
- package/android/src/main/java/expo/modules/atprotooauthclient/Records.kt +61 -0
- package/dist/ExpoAtprotoOAuthClientModule.d.ts +22 -0
- package/dist/ExpoAtprotoOAuthClientModule.d.ts.map +1 -0
- package/dist/ExpoAtprotoOAuthClientModule.js +3 -0
- package/dist/ExpoAtprotoOAuthClientModule.js.map +1 -0
- package/dist/ExpoAtprotoOAuthClientModule.types.d.ts +2 -0
- package/dist/ExpoAtprotoOAuthClientModule.types.d.ts.map +1 -0
- package/dist/ExpoAtprotoOAuthClientModule.types.js +2 -0
- package/dist/ExpoAtprotoOAuthClientModule.types.js.map +1 -0
- package/dist/expo-oauth-client-interface.d.ts +6 -0
- package/dist/expo-oauth-client-interface.d.ts.map +1 -0
- package/dist/expo-oauth-client-interface.js +2 -0
- package/dist/expo-oauth-client-interface.js.map +1 -0
- package/dist/expo-oauth-client-options.d.ts +9 -0
- package/dist/expo-oauth-client-options.d.ts.map +1 -0
- package/dist/expo-oauth-client-options.js +2 -0
- package/dist/expo-oauth-client-options.js.map +1 -0
- package/dist/expo-oauth-client.native.d.ts +13 -0
- package/dist/expo-oauth-client.native.d.ts.map +1 -0
- package/dist/expo-oauth-client.native.js +130 -0
- package/dist/expo-oauth-client.native.js.map +1 -0
- package/dist/expo-oauth-client.web.d.ts +9 -0
- package/dist/expo-oauth-client.web.d.ts.map +1 -0
- package/dist/expo-oauth-client.web.js +24 -0
- package/dist/expo-oauth-client.web.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/polyfill.d.ts +5 -0
- package/dist/polyfill.d.ts.map +1 -0
- package/dist/polyfill.js +5 -0
- package/dist/polyfill.js.map +1 -0
- package/dist/utils/expo-key.d.ts +11 -0
- package/dist/utils/expo-key.d.ts.map +1 -0
- package/dist/utils/expo-key.js +29 -0
- package/dist/utils/expo-key.js.map +1 -0
- package/dist/utils/mmkv-simple-store-ttl.d.ts +24 -0
- package/dist/utils/mmkv-simple-store-ttl.d.ts.map +1 -0
- package/dist/utils/mmkv-simple-store-ttl.js +62 -0
- package/dist/utils/mmkv-simple-store-ttl.js.map +1 -0
- package/dist/utils/mmkv-simple-store.d.ts +18 -0
- package/dist/utils/mmkv-simple-store.d.ts.map +1 -0
- package/dist/utils/mmkv-simple-store.js +31 -0
- package/dist/utils/mmkv-simple-store.js.map +1 -0
- package/dist/utils/stores.d.ts +24 -0
- package/dist/utils/stores.d.ts.map +1 -0
- package/dist/utils/stores.js +99 -0
- package/dist/utils/stores.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Crypto.swift +83 -0
- package/ios/ExpoAtprotoOAuthClient.podspec +31 -0
- package/ios/ExpoAtprotoOAuthClientModule.swift +45 -0
- package/ios/Jose.swift +137 -0
- package/ios/Records.swift +58 -0
- package/package.json +52 -0
- package/src/ExpoAtprotoOAuthClientModule.ts +33 -0
- package/src/ExpoAtprotoOAuthClientModule.types.ts +2 -0
- package/src/expo-oauth-client-interface.ts +10 -0
- package/src/expo-oauth-client-options.ts +27 -0
- package/src/expo-oauth-client.d.ts +6 -0
- package/src/expo-oauth-client.native.ts +111 -0
- package/src/expo-oauth-client.web.ts +42 -0
- package/src/index.ts +4 -0
- package/src/polyfill.ts +4 -0
- package/src/utils/expo-key.ts +50 -0
- package/src/utils/mmkv-simple-store-ttl.ts +90 -0
- package/src/utils/mmkv-simple-store.ts +48 -0
- package/src/utils/stores.ts +115 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Jwk,
|
|
3
|
+
type JwtHeader,
|
|
4
|
+
type JwtPayload,
|
|
5
|
+
Key,
|
|
6
|
+
type SignedJwt,
|
|
7
|
+
type VerifyOptions,
|
|
8
|
+
type VerifyResult,
|
|
9
|
+
} from '@atproto/oauth-client'
|
|
10
|
+
import type { NativeJwk } from '../ExpoAtprotoOAuthClientModule'
|
|
11
|
+
import { default as NativeModule } from '../ExpoAtprotoOAuthClientModule'
|
|
12
|
+
|
|
13
|
+
export type ExpoJwk = Jwk & NativeJwk & { key_ops: ['sign'] }
|
|
14
|
+
export class ExpoKey extends Key<ExpoJwk> {
|
|
15
|
+
async createJwt(header: JwtHeader, payload: JwtPayload): Promise<SignedJwt> {
|
|
16
|
+
return NativeModule.createJwt(
|
|
17
|
+
JSON.stringify(header),
|
|
18
|
+
JSON.stringify(payload),
|
|
19
|
+
toNativeJwk(this.jwk),
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async verifyJwt<C extends string = never>(
|
|
24
|
+
token: SignedJwt,
|
|
25
|
+
options: VerifyOptions<C> = {},
|
|
26
|
+
): Promise<VerifyResult<C>> {
|
|
27
|
+
return NativeModule.verifyJwt(token, toNativeJwk(this.jwk), options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async generate(algs: string[]): Promise<ExpoKey> {
|
|
31
|
+
if (algs.includes('ES256')) {
|
|
32
|
+
const jwk = await NativeModule.generatePrivateJwk('ES256')
|
|
33
|
+
return new ExpoKey({ ...jwk, key_ops: ['sign'] })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw TypeError(`No supported algorithm found in: ${algs.join(', ')}`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toNativeJwk(jwk: ExpoJwk): NativeJwk {
|
|
41
|
+
return {
|
|
42
|
+
kty: jwk.kty,
|
|
43
|
+
crv: jwk.crv,
|
|
44
|
+
kid: jwk.kid,
|
|
45
|
+
x: jwk.x,
|
|
46
|
+
y: jwk.y,
|
|
47
|
+
d: jwk.d,
|
|
48
|
+
alg: jwk.alg,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Configuration, MMKV } from 'react-native-mmkv'
|
|
2
|
+
import type { SimpleStore, Value } from '@atproto-labs/simple-store'
|
|
3
|
+
import { MMKVSimpleStore, MMKVSimpleStoreOptions } from './mmkv-simple-store'
|
|
4
|
+
|
|
5
|
+
export type MMKVSimpleStoreTTLOptions<V extends Value> =
|
|
6
|
+
MMKVSimpleStoreOptions<V> & {
|
|
7
|
+
clearInterval?: null | false | number
|
|
8
|
+
expiresAt: (value: V) => null | number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A {@link SimpleStore} implementation based on {@link MMKVSimpleStore} that
|
|
13
|
+
* supports expiring entries after a certain time.
|
|
14
|
+
*/
|
|
15
|
+
export class MMKVSimpleStoreTTL<V extends Value>
|
|
16
|
+
extends MMKVSimpleStore<V>
|
|
17
|
+
implements Disposable, SimpleStore<string, V>
|
|
18
|
+
{
|
|
19
|
+
readonly #store: MMKV
|
|
20
|
+
readonly #expiresAt: (value: V) => null | number
|
|
21
|
+
readonly #clearTimer?: ReturnType<typeof setInterval>
|
|
22
|
+
|
|
23
|
+
constructor({
|
|
24
|
+
clearInterval = 60 * 1e3,
|
|
25
|
+
expiresAt,
|
|
26
|
+
encode,
|
|
27
|
+
decode,
|
|
28
|
+
|
|
29
|
+
...config
|
|
30
|
+
}: MMKVSimpleStoreTTLOptions<V> & Configuration) {
|
|
31
|
+
super({ ...config, encode, decode })
|
|
32
|
+
|
|
33
|
+
this.#store = new MMKV({ ...config, id: `${config.id}.exp` })
|
|
34
|
+
this.#expiresAt = expiresAt
|
|
35
|
+
if (clearInterval) {
|
|
36
|
+
this.#clearTimer = setInterval(() => this.clearExpired(), clearInterval)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.clearExpired()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[Symbol.dispose]() {
|
|
43
|
+
clearInterval(this.#clearTimer)
|
|
44
|
+
this.clearExpired()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override set(key: string, value: V): void {
|
|
48
|
+
super.set(key, value)
|
|
49
|
+
|
|
50
|
+
const expirationDate = this.#expiresAt.call(null, value)
|
|
51
|
+
if (expirationDate == null) this.#store.delete(key)
|
|
52
|
+
else this.#store.set(key, expirationDate)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override get(key: string): V | undefined {
|
|
56
|
+
if (this.isExpired(key)) {
|
|
57
|
+
this.del(key)
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return super.get(key)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override del(key: string): void {
|
|
65
|
+
super.del(key)
|
|
66
|
+
this.#store.delete(key)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override clear(): void {
|
|
70
|
+
super.clear()
|
|
71
|
+
this.#store.clearAll()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getExpirationTime(key: string): number | undefined {
|
|
75
|
+
return this.#store.getNumber(key) ?? undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isExpired(key: string): boolean {
|
|
79
|
+
const expirationTime = this.getExpirationTime(key)
|
|
80
|
+
return expirationTime != null && expirationTime < Date.now()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clearExpired() {
|
|
84
|
+
for (const key of this.#store.getAllKeys() ?? []) {
|
|
85
|
+
if (this.isExpired(key)) {
|
|
86
|
+
this.del(key)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Configuration, MMKV } from 'react-native-mmkv'
|
|
2
|
+
import type { SimpleStore, Value } from '@atproto-labs/simple-store'
|
|
3
|
+
|
|
4
|
+
export type MMKVSimpleStoreOptions<V extends Value> = {
|
|
5
|
+
decode: (value: string) => V
|
|
6
|
+
encode: (value: V) => string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A {@link SimpleStore} implementation using {@link MMKV} for storage.
|
|
11
|
+
*/
|
|
12
|
+
export class MMKVSimpleStore<V extends Value>
|
|
13
|
+
implements SimpleStore<string, V>
|
|
14
|
+
{
|
|
15
|
+
readonly #store: MMKV
|
|
16
|
+
readonly #encode: (value: V) => string
|
|
17
|
+
readonly #decode: (value: string) => V
|
|
18
|
+
|
|
19
|
+
constructor({
|
|
20
|
+
decode,
|
|
21
|
+
encode,
|
|
22
|
+
...config
|
|
23
|
+
}: MMKVSimpleStoreOptions<V> & Configuration) {
|
|
24
|
+
this.#store = new MMKV(config)
|
|
25
|
+
this.#decode = decode
|
|
26
|
+
this.#encode = encode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
set(key: string, value: V): void {
|
|
30
|
+
const encoded = this.#encode.call(null, value)
|
|
31
|
+
this.#store.set(key, encoded)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get(key: string): V | undefined {
|
|
35
|
+
const value = this.#store.getString(key)
|
|
36
|
+
if (value === undefined) return undefined
|
|
37
|
+
|
|
38
|
+
return this.#decode.call(null, value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
del(key: string): void {
|
|
42
|
+
this.#store.delete(key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
clear() {
|
|
46
|
+
this.#store.clearAll()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DidDocument,
|
|
3
|
+
InternalStateData,
|
|
4
|
+
OAuthAuthorizationServerMetadata,
|
|
5
|
+
OAuthProtectedResourceMetadata,
|
|
6
|
+
ResolvedHandle,
|
|
7
|
+
Session,
|
|
8
|
+
} from '@atproto/oauth-client'
|
|
9
|
+
import { ExpoKey } from './expo-key'
|
|
10
|
+
import { MMKVSimpleStoreTTL } from './mmkv-simple-store-ttl'
|
|
11
|
+
|
|
12
|
+
const MMKV_ID = 'expo-atproto-oauth-client'
|
|
13
|
+
|
|
14
|
+
export class AuthorizationServerMetadataCache extends MMKVSimpleStoreTTL<OAuthAuthorizationServerMetadata> {
|
|
15
|
+
constructor() {
|
|
16
|
+
super({
|
|
17
|
+
id: `${MMKV_ID}.authorizationServerMetadata`,
|
|
18
|
+
expiresAt: oneMinuteFromNow,
|
|
19
|
+
decode: JSON.parse,
|
|
20
|
+
encode: JSON.stringify,
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ProtectedResourceMetadataCache extends MMKVSimpleStoreTTL<OAuthProtectedResourceMetadata> {
|
|
26
|
+
constructor() {
|
|
27
|
+
super({
|
|
28
|
+
id: `${MMKV_ID}.protectedResourceMetadata`,
|
|
29
|
+
expiresAt: oneMinuteFromNow,
|
|
30
|
+
decode: JSON.parse,
|
|
31
|
+
encode: JSON.stringify,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class DpopNonceCache extends MMKVSimpleStoreTTL<string> {
|
|
37
|
+
constructor() {
|
|
38
|
+
super({
|
|
39
|
+
id: `${MMKV_ID}.dpopNonce`,
|
|
40
|
+
expiresAt: tenMinutesFromNow,
|
|
41
|
+
decode: identity,
|
|
42
|
+
encode: identity,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class DidCache extends MMKVSimpleStoreTTL<DidDocument> {
|
|
48
|
+
constructor() {
|
|
49
|
+
super({
|
|
50
|
+
id: `${MMKV_ID}.did`,
|
|
51
|
+
expiresAt: oneMinuteFromNow,
|
|
52
|
+
decode: JSON.parse,
|
|
53
|
+
encode: JSON.stringify,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class HandleCache extends MMKVSimpleStoreTTL<ResolvedHandle> {
|
|
59
|
+
constructor() {
|
|
60
|
+
super({
|
|
61
|
+
id: `${MMKV_ID}.handle`,
|
|
62
|
+
expiresAt: oneMinuteFromNow,
|
|
63
|
+
decode: JSON.parse,
|
|
64
|
+
encode: JSON.stringify,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class StateStore extends MMKVSimpleStoreTTL<InternalStateData> {
|
|
70
|
+
constructor() {
|
|
71
|
+
super({
|
|
72
|
+
id: `${MMKV_ID}.state`,
|
|
73
|
+
expiresAt: tenMinutesFromNow,
|
|
74
|
+
decode: (value) => {
|
|
75
|
+
const parsed = JSON.parse(value)
|
|
76
|
+
return { ...parsed, dpopKey: new ExpoKey(parsed.dpopKey) }
|
|
77
|
+
},
|
|
78
|
+
encode: (value) => {
|
|
79
|
+
return JSON.stringify({ ...value, dpopKey: value.dpopKey.jwk })
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class SessionStore extends MMKVSimpleStoreTTL<Session> {
|
|
86
|
+
constructor() {
|
|
87
|
+
super({
|
|
88
|
+
id: `${MMKV_ID}.session`,
|
|
89
|
+
expiresAt: ({ tokenSet }) => {
|
|
90
|
+
if (tokenSet.refresh_token) return null
|
|
91
|
+
if (tokenSet.expires_at) return new Date(tokenSet.expires_at).valueOf()
|
|
92
|
+
return null
|
|
93
|
+
},
|
|
94
|
+
decode: (value) => {
|
|
95
|
+
const parsed = JSON.parse(value)
|
|
96
|
+
return { ...parsed, dpopKey: new ExpoKey(parsed.dpopKey) }
|
|
97
|
+
},
|
|
98
|
+
encode: (value) => {
|
|
99
|
+
return JSON.stringify({ ...value, dpopKey: value.dpopKey.jwk })
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function identity<T>(x: T): T {
|
|
106
|
+
return x
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function tenMinutesFromNow() {
|
|
110
|
+
return Date.now() + 10 * 60e3
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function oneMinuteFromNow() {
|
|
114
|
+
return Date.now() + 60e3
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/ExpoAtprotoOAuthClientModule.ts","./src/ExpoAtprotoOAuthClientModule.types.ts","./src/expo-oauth-client-interface.ts","./src/expo-oauth-client-options.ts","./src/expo-oauth-client.d.ts","./src/expo-oauth-client.native.ts","./src/expo-oauth-client.web.ts","./src/index.ts","./src/polyfill.ts","./src/utils/expo-key.ts","./src/utils/mmkv-simple-store-ttl.ts","./src/utils/mmkv-simple-store.ts","./src/utils/stores.ts"],"version":"5.8.3"}
|
package/tsconfig.json
ADDED