Skip to content

Commit

Permalink
feat(oauth-client-react-native): shell
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieusieben committed Apr 24, 2024
1 parent 7e15208 commit 2a063dc
Show file tree
Hide file tree
Showing 23 changed files with 3,571 additions and 91 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"pino-pretty": "^9.1.0",
"prettier": "^3.2.5",
"prettier-config-standard": "^7.0.0",
"react-native": "^0.73.6",
"typescript": "^5.4.4"
},
"workspaces": {
Expand Down
94 changes: 94 additions & 0 deletions packages/oauth-client-react-native/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["OauthClientReactNative_kotlinVersion"]

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}

apply plugin: "com.android.library"
apply plugin: "kotlin-android"

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["OauthClientReactNative_" + name]
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["OauthClientReactNative_" + name]).toInteger()
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()

// Namespace support was added in 7.3.0
return (major == 7 && minor >= 3) || major >= 8
}

android {
if (supportsNamespace()) {
namespace "com.oauthclientreactnative"

sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
}
}
}

compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")

}

buildTypes {
release {
minifyEnabled false
}
}

lintOptions {
disable "GradleCompatible"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

repositories {
mavenCentral()
google()
}

def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

5 changes: 5 additions & 0 deletions packages/oauth-client-react-native/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OauthClientReactNative_kotlinVersion=1.7.0
OauthClientReactNative_minSdkVersion=21
OauthClientReactNative_targetSdkVersion=31
OauthClientReactNative_compileSdkVersion=31
OauthClientReactNative_ndkversion=21.4.7075529
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.oauthclientreactnative">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.oauthclientreactnative

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

class OauthClientReactNativeModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

override fun getName(): String {
return NAME
}

// Example method
// See https://reactnative.dev/docs/native-modules-android
@ReactMethod
fun multiply(a: Double, b: Double, promise: Promise) {
promise.resolve(a * b)
}

companion object {
const val NAME = "OauthClientReactNative"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.oauthclientreactnative

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager


class OauthClientReactNativePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(OauthClientReactNativeModule(reactContext))
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>
14 changes: 14 additions & 0 deletions packages/oauth-client-react-native/ios/OauthClientReactNative.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(OauthClientReactNative, NSObject)

RCT_EXTERN_METHOD(multiply:(float)a withB:(float)b
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)

+ (BOOL)requiresMainQueueSetup
{
return NO;
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@objc(OauthClientReactNative)
class OauthClientReactNative: NSObject {

@objc(multiply:withB:withResolver:withRejecter:)
func multiply(a: Float, b: Float, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
resolve(a*b)
}
}
42 changes: 42 additions & 0 deletions packages/oauth-client-react-native/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@atproto/oauth-client-react-native",
"version": "0.0.1",
"license": "MIT",
"description": "Implementation of ATPROTO OAuth client for react-native",
"keywords": [
"atproto",
"oauth",
"client",
"react-native"
],
"homepage": "https://atproto.com",
"repository": {
"type": "git",
"url": "https://github.com/bluesky-social/atproto",
"directory": "packages/oauth-client-react-native"
},
"type": "commonjs",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc --build tsconfig.build.json"
},
"dependencies": {
"@atproto/caching": "workspace:*",
"@atproto/fetch": "workspace:*",
"@atproto/handle-resolver": "workspace:*",
"@atproto/identity-resolver": "workspace:*",
"@atproto/jwk": "workspace:*",
"@atproto/oauth-client": "workspace:*",
"@atproto/oauth-client-metadata": "workspace:*",
"@atproto/oauth-server-metadata-resolver": "workspace:*"
},
"devDependencies": {
"react-native": "0.73.6",
"typescript": "^5.3.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}
1 change: 1 addition & 0 deletions packages/oauth-client-react-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './react-native-oauth-client-factory.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NativeModules, Platform } from 'react-native'

const LINKING_ERROR =
`The package 'oauth-client-react-native' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo Go\n'

export const OauthClientReactNative = NativeModules.OauthClientReactNative
? NativeModules.OauthClientReactNative
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR)
},
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
CryptoImplementation,
DigestAlgorithm,
Key,
} from '@atproto/oauth-client'

import { OauthClientReactNative } from './oauth-client-react-native.js'
import { ReactNativeKey } from './react-native-key.js'

export class ReactNativeCryptoImplementation implements CryptoImplementation {
async createKey(algs: string[]): Promise<Key> {
const bytes = await this.getRandomValues(12)
const kid = Array.from(bytes, byteToHex).join('')
return ReactNativeKey.generate(kid, algs)
}

async getRandomValues(length: number): Promise<Uint8Array> {
return OauthClientReactNative.getRandomValues(length)
}

async digest(
bytes: Uint8Array,
algorithm: DigestAlgorithm,
): Promise<Uint8Array> {
return OauthClientReactNative.digest(bytes, algorithm)
}
}

function byteToHex(b: number): string {
return b.toString(16).padStart(2, '0')
}
39 changes: 39 additions & 0 deletions packages/oauth-client-react-native/src/react-native-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Jwk,
Jwt,
JwtHeader,
JwtPayload,
Key,
VerifyOptions,
VerifyPayload,
VerifyResult,
} from '@atproto/jwk'

import { OauthClientReactNative } from './oauth-client-react-native.js'

export class ReactNativeKey extends Key {
static async generate(
kid: string,
allowedAlgos: string[] = ['ES256'],
): Promise<ReactNativeKey> {
if (!allowedAlgos.includes('ES256')) {
throw new Error(
`None of the allowed algorithms (${allowedAlgos}) are supported (only ES256)`,
)
}

const privateJwk: Jwk = await OauthClientReactNative.createES256Jwk()
return new ReactNativeKey({ ...privateJwk, kid })
}

async createJwt(header: JwtHeader, payload: JwtPayload): Promise<Jwt> {
return OauthClientReactNative.createJwt(header, payload, this.jwk)
}

async verifyJwt<
P extends VerifyPayload = JwtPayload,
C extends string = string,
>(token: Jwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> {
return OauthClientReactNative.verifyJwt(token, options, this.jwk)
}
}

0 comments on commit 2a063dc

Please sign in to comment.