-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Bluesky OAuth Client #3473
Closed
Closed
Bluesky OAuth Client #3473
Changes from 7 commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
cfd12f3
rm old login code
haileyok 9128cb1
rm some more code and add some new code
haileyok 103d441
remove some more unnecessary stuff
haileyok 6856b76
add auth session browser for native
haileyok 0c6eebf
remove unnecessary
haileyok fd8e886
a simple redirect on web
haileyok b4741a8
change
haileyok 55acb43
merge main
haileyok 872f5c0
merge main
haileyok c9c8e00
adjust `app.config.js` to prevent development manifest error
haileyok 02697bb
rev change
haileyok b295e84
add oauth client
haileyok 28acdf7
remove
haileyok aa32f84
Merge branch 'main' into hailey/oauth
haileyok 857503e
Merge branch 'main' into hailey/oauth
haileyok d61fc5f
add `expo-secure-store`
haileyok 71721b5
copy over a few files for now
haileyok 352d375
save
haileyok db750e3
add `rn-quick-crypto` and `rn-quick-base64`
haileyok e165d49
metro config "polyfill"
haileyok ec58082
add jwk lib
haileyok 1461c0a
decent base
haileyok c649795
remove all the test files
haileyok 6522853
add `expo-sqlite`
haileyok ba4e95f
update deps (last time)
haileyok f7e8946
Merge branch 'main' into hailey/oauth
haileyok e831bc1
native crypto setup
haileyok 25e5ee7
remove bogus packages
haileyok 13607ed
squash
haileyok b521933
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok 0aab04f
fix names
haileyok def2f77
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok 72e22a2
revert babel changes
haileyok 70251e5
few small changes
haileyok d948e73
update dev variables
haileyok 73c98bc
native factory impl
haileyok 00abca9
few more cleanups
haileyok 472dbf5
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok 951f5f2
it works!
haileyok bc67bdb
rm some useless code
haileyok c97e439
better layout
haileyok 71f1e44
add jwt struct
haileyok 2ada0cb
better implementation
haileyok 2c1b370
swift impl
haileyok 9f6db0e
few fixes
haileyok f4a2362
rm old files now that we have the skeleton ready
haileyok a07d291
add structs
haileyok adab484
oops
haileyok 37f5840
update genkeypair
haileyok caddbeb
android impl
haileyok e01a127
rm log
haileyok de9f9ce
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok 3e5a3ac
create factory (copy db for now)
haileyok bb4f973
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok db80f6e
changes
haileyok 56f2baf
fix
haileyok dbbcd27
Merge branch 'hailey/expo-oauth-helper' into hailey/oauth
haileyok d56116a
few more things
haileyok c499a03
fix android
haileyok 7ed21e4
rely on zod to remove os specific restraints
haileyok f464875
simplify kt
haileyok 20f2988
more progress working through this
haileyok c7e2909
metro config temp
haileyok 0d85013
feat(dev): allow using @atproto packages from another directory
matthieusieben 77afff2
Merge branch 'dx-atproto-modules' into hailey/oauth
haileyok ea75b63
Merge branch 'hailey/oauth' into hailey/expo-oauth-helper
haileyok File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
apply plugin: 'com.android.library' | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'maven-publish' | ||
|
||
group = 'expo.modules.blueskyoauthclient' | ||
version = '0.0.1' | ||
|
||
buildscript { | ||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") | ||
if (expoModulesCorePlugin.exists()) { | ||
apply from: expoModulesCorePlugin | ||
applyKotlinExpoModulesCorePlugin() | ||
} | ||
|
||
// Simple helper that allows the root project to override versions declared by this library. | ||
ext.safeExtGet = { prop, fallback -> | ||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
} | ||
|
||
// Ensures backward compatibility | ||
ext.getKotlinVersion = { | ||
if (ext.has("kotlinVersion")) { | ||
ext.kotlinVersion() | ||
} else { | ||
ext.safeExtGet("kotlinVersion", "1.8.10") | ||
} | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") | ||
} | ||
} | ||
|
||
afterEvaluate { | ||
publishing { | ||
publications { | ||
release(MavenPublication) { | ||
from components.release | ||
} | ||
} | ||
repositories { | ||
maven { | ||
url = mavenLocal().url | ||
} | ||
} | ||
} | ||
} | ||
|
||
android { | ||
compileSdkVersion safeExtGet("compileSdkVersion", 33) | ||
|
||
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION | ||
if (agpVersion.tokenize('.')[0].toInteger() < 8) { | ||
compileOptions { | ||
sourceCompatibility JavaVersion.VERSION_11 | ||
targetCompatibility JavaVersion.VERSION_11 | ||
} | ||
|
||
kotlinOptions { | ||
jvmTarget = JavaVersion.VERSION_11.majorVersion | ||
} | ||
} | ||
|
||
namespace "expo.modules.blueskyoauthclient" | ||
defaultConfig { | ||
minSdkVersion safeExtGet("minSdkVersion", 21) | ||
targetSdkVersion safeExtGet("targetSdkVersion", 34) | ||
versionCode 1 | ||
versionName "0.0.1" | ||
} | ||
lintOptions { | ||
abortOnError false | ||
} | ||
publishing { | ||
singleVariant("release") { | ||
withSourcesJar() | ||
} | ||
} | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation project(':expo-modules-core') | ||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" | ||
implementation "com.nimbusds:nimbus-jose-jwt:9.38-rc3" | ||
} |
2 changes: 2 additions & 0 deletions
2
modules/expo-bluesky-oauth-client/android/src/main/AndroidManifest.xml
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,2 @@ | ||
<manifest> | ||
</manifest> |
52 changes: 52 additions & 0 deletions
52
...-bluesky-oauth-client/android/src/main/java/expo/modules/blueskyoauthclient/CryptoUtil.kt
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,52 @@ | ||
package expo.modules.blueskyoauthclient | ||
|
||
import com.nimbusds.jose.Algorithm | ||
import java.security.KeyPairGenerator | ||
import java.security.MessageDigest | ||
import java.security.interfaces.ECPublicKey | ||
import java.security.interfaces.ECPrivateKey | ||
import com.nimbusds.jose.jwk.Curve | ||
import com.nimbusds.jose.jwk.ECKey | ||
import com.nimbusds.jose.jwk.KeyUse | ||
import java.util.UUID | ||
|
||
class CryptoUtil { | ||
fun digest(data: ByteArray): ByteArray { | ||
val digest = MessageDigest.getInstance("sha256") | ||
return digest.digest(data) | ||
} | ||
|
||
fun getRandomValues(byteLength: Int): ByteArray { | ||
val random = ByteArray(byteLength) | ||
java.security.SecureRandom().nextBytes(random) | ||
return random | ||
} | ||
|
||
fun generateKeyPair(keyId: String?): Pair<String, String> { | ||
val keyIdString = keyId ?: UUID.randomUUID().toString() | ||
|
||
val keyPairGen = KeyPairGenerator.getInstance("EC") | ||
keyPairGen.initialize(Curve.P_256.toECParameterSpec()) | ||
val keyPair = keyPairGen.generateKeyPair() | ||
|
||
val publicKey = keyPair.public as ECPublicKey | ||
val privateKey = keyPair.private as ECPrivateKey | ||
|
||
val publicJwk = ECKey.Builder(Curve.P_256, publicKey) | ||
.keyUse(KeyUse.SIGNATURE) | ||
.algorithm(Algorithm.parse("ES256")) | ||
.keyID(keyIdString) | ||
.build() | ||
val privateJwk = ECKey.Builder(Curve.P_256, publicKey) | ||
.privateKey(privateKey) | ||
.keyUse(KeyUse.SIGNATURE) | ||
.keyID(keyIdString) | ||
.algorithm(Algorithm.parse("ES256")) | ||
.build() | ||
|
||
return Pair( | ||
publicJwk.toString(), | ||
privateJwk.toString() | ||
) | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...ent/android/src/main/java/expo/modules/blueskyoauthclient/ExpoBlueskyOAuthClientModule.kt
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,35 @@ | ||
package expo.modules.blueskyoauthclient | ||
|
||
import expo.modules.kotlin.modules.Module | ||
import expo.modules.kotlin.modules.ModuleDefinition | ||
|
||
class ExpoBlueskyOAuthClientModule : Module() { | ||
override fun definition() = ModuleDefinition { | ||
Name("ExpoBlueskyOAuthClient") | ||
|
||
AsyncFunction("digest") { value: ByteArray -> | ||
return@AsyncFunction CryptoUtil().digest(value) | ||
} | ||
|
||
Function("getRandomValues") { byteLength: Int -> | ||
return@Function CryptoUtil().getRandomValues(byteLength) | ||
} | ||
|
||
AsyncFunction("generateKeyPair") { keyId: String? -> | ||
val res = CryptoUtil().generateKeyPair(keyId) | ||
|
||
return@AsyncFunction mapOf( | ||
"publicKey" to res.first, | ||
"privateKey" to res.second | ||
) | ||
} | ||
|
||
AsyncFunction("createJwt") { jwkString: String, headerString: String, payloadString: String -> | ||
return@AsyncFunction JWTUtil().createJwt(jwkString, headerString, payloadString) | ||
} | ||
|
||
AsyncFunction("verifyJwt") { jwkString: String, tokenString: String, options: String? -> | ||
return@AsyncFunction JWTUtil().verifyJwt(jwkString, tokenString, options) | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...xpo-bluesky-oauth-client/android/src/main/java/expo/modules/blueskyoauthclient/JWTUtil.kt
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,35 @@ | ||
package expo.modules.blueskyoauthclient | ||
|
||
import com.nimbusds.jose.JWSHeader | ||
import com.nimbusds.jose.crypto.ECDSASigner | ||
import com.nimbusds.jose.crypto.ECDSAVerifier | ||
import com.nimbusds.jose.jwk.ECKey | ||
import com.nimbusds.jwt.JWTClaimsSet | ||
import com.nimbusds.jwt.SignedJWT | ||
|
||
|
||
class JWTUtil { | ||
fun createJwt(jwkString: String, headerString: String, payloadString: String): String { | ||
val key = ECKey.parse(jwkString) | ||
val header = JWSHeader.parse(headerString) | ||
val payload = JWTClaimsSet.parse(payloadString) | ||
|
||
val signer = ECDSASigner(key) | ||
val jwt = SignedJWT(header, payload) | ||
jwt.sign(signer) | ||
|
||
return jwt.serialize() | ||
} | ||
|
||
fun verifyJwt(jwkString: String, tokenString: String, options: String?): Boolean { | ||
return try { | ||
val key = ECKey.parse(jwkString) | ||
val jwt = SignedJWT.parse(tokenString) | ||
val verifier = ECDSAVerifier(key) | ||
|
||
jwt.verify(verifier) | ||
} catch(e: Exception) { | ||
false | ||
} | ||
} | ||
} |
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,9 @@ | ||
{ | ||
"platforms": ["ios", "tvos", "android", "web"], | ||
"ios": { | ||
"modules": ["ExpoBlueskyOAuthClientModule"] | ||
}, | ||
"android": { | ||
"modules": ["expo.modules.blueskyoauthclient.ExpoBlueskyOAuthClientModule"] | ||
} | ||
} |
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,4 @@ | ||
export * from './src/oauth-client-react-native' | ||
export * from './src/react-native-crypto-implementation' | ||
export * from './src/react-native-key' | ||
export * from './src/react-native-store-with-key' | ||
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,64 @@ | ||
import CryptoKit | ||
import JOSESwift | ||
import ExpoModulesCore | ||
|
||
class CryptoUtil { | ||
// The equivalent of crypto.subtle.digest() with JS on web | ||
public static func digest(data: Data) -> Data { | ||
let hash = SHA256.hash(data: data) | ||
return Data(hash) | ||
} | ||
|
||
public static func getRandomValues(byteLength: Int) -> Data { | ||
let bytes = (0..<byteLength).map { _ in UInt8.random(in: UInt8.min...UInt8.max) } | ||
return Data(bytes) | ||
} | ||
|
||
public static func generateKeyPair() throws -> JWKPair? { | ||
let keyIdString = UUID().uuidString | ||
|
||
let privateKey = P256.Signing.PrivateKey() | ||
let publicKey = privateKey.publicKey | ||
|
||
let x = publicKey.x963Representation[1..<33].base64URLEncodedString() | ||
let y = publicKey.x963Representation[33...].base64URLEncodedString() | ||
let d = privateKey.rawRepresentation.base64URLEncodedString() | ||
|
||
let publicJWK = JWK( | ||
alg: "ES256".toField(), | ||
kty: "EC".toField(), | ||
crv: "P-256".toNullableField(), | ||
x: x.toNullableField(), | ||
y: y.toNullableField(), | ||
use: "sig".toNullableField(), | ||
kid: keyIdString.toNullableField() | ||
) | ||
let privateJWK = JWK( | ||
alg: "ES256".toField(), | ||
kty: "EC".toField(), | ||
crv: "P-256".toNullableField(), | ||
x: x.toNullableField(), | ||
y: y.toNullableField(), | ||
d: d.toNullableField(), | ||
use: "sig".toNullableField(), | ||
kid: keyIdString.toNullableField() | ||
) | ||
|
||
return JWKPair(privateKey: privateJWK.toField(), publicKey: publicJWK.toField()) | ||
} | ||
} | ||
|
||
extension Data { | ||
func base64URLEncodedString() -> String { | ||
return self.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") | ||
} | ||
} | ||
|
||
extension String { | ||
func toField() -> Field<String> { | ||
return Field(wrappedValue: self) | ||
} | ||
func toNullableField() -> Field<String?> { | ||
return Field(wrappedValue: self) | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClient.podspec
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,22 @@ | ||
Pod::Spec.new do |s| | ||
s.name = 'ExpoBlueskyOAuthClient' | ||
s.version = '0.0.1' | ||
s.summary = 'A library of native functions to support Bluesky OAuth in React Native.' | ||
s.description = 'A library of native functions to support Bluesky OAuth in React Native.' | ||
s.author = '' | ||
s.homepage = 'https://github.com/bluesky-social/social-app' | ||
s.platforms = { :ios => '13.4', :tvos => '13.4' } | ||
s.source = { git: '' } | ||
s.static_framework = true | ||
|
||
s.dependency 'ExpoModulesCore' | ||
s.dependency 'JOSESwift', '~> 2.3' | ||
|
||
# Swift/Objective-C compatibility | ||
s.pod_target_xcconfig = { | ||
'DEFINES_MODULE' => 'YES', | ||
'SWIFT_COMPILATION_MODE' => 'wholemodule' | ||
} | ||
|
||
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" | ||
end |
45 changes: 45 additions & 0 deletions
45
modules/expo-bluesky-oauth-client/ios/ExpoBlueskyOAuthClientModule.swift
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,45 @@ | ||
import ExpoModulesCore | ||
import JOSESwift | ||
|
||
public class ExpoBlueskyOAuthClientModule: Module { | ||
public func definition() -> ModuleDefinition { | ||
Name("ExpoBlueskyOAuthClient") | ||
|
||
AsyncFunction("digest") { (data: Data, promise: Promise) in | ||
promise.resolve(CryptoUtil.digest(data: data)) | ||
} | ||
|
||
// We are going to leave this as sync to line up the APIs. It's fast, so not a big deal. | ||
Function("getRandomValues") { (byteLength: Int) in | ||
return CryptoUtil.getRandomValues(byteLength: byteLength) | ||
} | ||
|
||
AsyncFunction ("generateJwk") { (algo: String?, promise: Promise) in | ||
if algo != "ES256" { | ||
promise.reject("GenerateKeyError", "Algorithim not supported.") | ||
return | ||
} | ||
|
||
let keypair = try? CryptoUtil.generateKeyPair() | ||
|
||
guard keypair != nil else { | ||
promise.reject("GenerateKeyError", "Error generating JWK.") | ||
return | ||
} | ||
|
||
promise.resolve(keypair) | ||
} | ||
|
||
AsyncFunction("createJwt") { (header: JWTHeader, payload: JWTPayload, jwk: JWK, promise: Promise) in | ||
guard let jwt = JWTUtil.createJwt(header: header, payload: payload, jwk: jwk) else { | ||
promise.reject("JWTError", "Error creating JWT.") | ||
return | ||
} | ||
promise.resolve(jwt) | ||
} | ||
|
||
AsyncFunction("verifyJwt") { (jwk: String, token: String, options: String?, promise: Promise) in | ||
promise.resolve(JWTUtil.verifyJwt(jwk, token: token, options: options)) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, this is probably a good place to expose the "universal" module by having an
index.web.ts
that exposes the same interfaces/classes (but from@atproto/oauth-client-browser
) ?