swift impl
This commit is contained in:
@@ -38,8 +38,8 @@ public class ExpoBlueskyOAuthClientModule: Module {
|
|||||||
promise.resolve(jwt)
|
promise.resolve(jwt)
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncFunction("verifyJwt") { (jwk: String, token: String, options: String?, promise: Promise) in
|
AsyncFunction("verifyJwt") { (token: String, jwk: JWK, promise: Promise) in
|
||||||
promise.resolve(JWTUtil.verifyJwt(jwk, token: token, options: options))
|
promise.resolve(JWTUtil.verifyJwt(token: token, jwk: jwk))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct JWK : Record {
|
|||||||
return Field(wrappedValue: self)
|
return Field(wrappedValue: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSecKey() throws -> SecKey? {
|
func toPrivateSecKey() throws -> SecKey? {
|
||||||
let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary())
|
let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary())
|
||||||
guard let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData),
|
guard let jwk = try? JSONDecoder().decode(ECPrivateKey.self, from: jsonData),
|
||||||
let key = try? jwk.converted(to: SecKey.self)
|
let key = try? jwk.converted(to: SecKey.self)
|
||||||
@@ -37,6 +37,17 @@ struct JWK : Record {
|
|||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPublicSecKey() throws -> SecKey? {
|
||||||
|
let jsonData = try JSONSerialization.data(withJSONObject: self.toDictionary())
|
||||||
|
guard let jwk = try? JSONDecoder().decode(ECPublicKey.self, from: jsonData),
|
||||||
|
let key = try? jwk.converted(to: SecKey.self)
|
||||||
|
else {
|
||||||
|
print("Error creating SecKey.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JWKPair : Record {
|
struct JWKPair : Record {
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ struct JWTHeader : Record {
|
|||||||
@Field
|
@Field
|
||||||
var jku: String?
|
var jku: String?
|
||||||
@Field
|
@Field
|
||||||
|
var jwk: JWK
|
||||||
|
@Field
|
||||||
var kid: String?
|
var kid: String?
|
||||||
@Field
|
@Field
|
||||||
var x5u: String?
|
|
||||||
@Field
|
|
||||||
var x5c: String?
|
|
||||||
@Field
|
|
||||||
var x5t: String?
|
|
||||||
@Field
|
|
||||||
var typ: String?
|
var typ: String?
|
||||||
@Field
|
@Field
|
||||||
var cty: String?
|
var cty: String?
|
||||||
@@ -179,3 +175,10 @@ struct JWTPayloadAuthorizationDetails : Record {
|
|||||||
return Field(wrappedValue: self)
|
return Field(wrappedValue: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct JWTVerifyResponse : Record {
|
||||||
|
@Field
|
||||||
|
var protectedHeader: JWTHeader
|
||||||
|
@Field
|
||||||
|
var payload: String
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ExpoModulesCore
|
||||||
import JOSESwift
|
import JOSESwift
|
||||||
|
|
||||||
class JWTUtil {
|
class JWTUtil {
|
||||||
@@ -34,7 +35,7 @@ class JWTUtil {
|
|||||||
public static func createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK) -> String? {
|
public static func createJwt(header: JWTHeader, payload: JWTPayload, jwk: JWK) -> String? {
|
||||||
guard let header = try? header.toJWSHeader(),
|
guard let header = try? header.toJWSHeader(),
|
||||||
let payload = try? payload.toPayload(),
|
let payload = try? payload.toPayload(),
|
||||||
let key = try? jwk.toSecKey()
|
let key = try? jwk.toPrivateSecKey()
|
||||||
else {
|
else {
|
||||||
print("didn't have one")
|
print("didn't have one")
|
||||||
return nil
|
return nil
|
||||||
@@ -52,15 +53,34 @@ class JWTUtil {
|
|||||||
return jws.compactSerializedString
|
return jws.compactSerializedString
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func verifyJwt(_ jwkString: String, token tokenString: String, options optionsString: String?) -> Bool {
|
public static func verifyJwt(token: String, jwk: JWK) -> JWTVerifyResponse? {
|
||||||
guard let key = try? jsonToPublicKey(jwkString),
|
guard let key = try? jwk.toPublicSecKey(),
|
||||||
let jws = try? JWS(compactSerialization: tokenString),
|
let jws = try? JWS(compactSerialization: token),
|
||||||
let verifier = Verifier(verifyingAlgorithm: .ES256, key: key),
|
let verifier = Verifier(verifyingAlgorithm: .ES256, key: key),
|
||||||
let isVerified = try? jws.validate(using: verifier).isValid(for: verifier)
|
let validation = try? jws.validate(using: verifier)
|
||||||
else {
|
else {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return isVerified
|
let header = validation.header
|
||||||
|
let serializedHeader = JWTHeader(
|
||||||
|
alg: "ES256",
|
||||||
|
jku: Field(wrappedValue: header.jku?.absoluteString),
|
||||||
|
kid: Field(wrappedValue:header.kid),
|
||||||
|
typ: Field(wrappedValue: header.typ),
|
||||||
|
cty: Field(wrappedValue: header.cty),
|
||||||
|
crit: Field(wrappedValue: header.cty)
|
||||||
|
)
|
||||||
|
|
||||||
|
let payload = String(data: validation.payload.data(), encoding: .utf8)
|
||||||
|
|
||||||
|
guard let payload = payload else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return JWTVerifyResponse(
|
||||||
|
protectedHeader: serializedHeader.toField(),
|
||||||
|
payload: payload.toField()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const OauthClientReactNative = (NativeModule as null) || {
|
|||||||
_token: Jwt,
|
_token: Jwt,
|
||||||
_jwk: Jwk,
|
_jwk: Jwk,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
payload: Record<string, unknown>
|
payload: string // this is a JSON response to make Swift a bit easier to work with
|
||||||
protectedHeader: Record<string, unknown>
|
protectedHeader: Record<string, unknown>
|
||||||
}> {
|
}> {
|
||||||
throw new Error(LINKING_ERROR)
|
throw new Error(LINKING_ERROR)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
jwkValidator,
|
jwkValidator,
|
||||||
Jwt,
|
Jwt,
|
||||||
JwtHeader,
|
JwtHeader,
|
||||||
jwtHeaderSchema,
|
|
||||||
JwtPayload,
|
JwtPayload,
|
||||||
jwtPayloadSchema,
|
jwtPayloadSchema,
|
||||||
Key,
|
Key,
|
||||||
@@ -45,8 +44,13 @@ export class ReactNativeKey extends Key {
|
|||||||
>(token: Jwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> {
|
>(token: Jwt, options?: VerifyOptions<C>): Promise<VerifyResult<P, C>> {
|
||||||
const result = await OauthClientReactNative.verifyJwt(token, this.jwk)
|
const result = await OauthClientReactNative.verifyJwt(token, this.jwk)
|
||||||
|
|
||||||
const payload = jwtPayloadSchema.parse(result.payload)
|
let payloadParsed = JSON.parse(result.payload)
|
||||||
const protectedHeader = jwtHeaderSchema.parse(result.protectedHeader)
|
payloadParsed = Object.fromEntries(
|
||||||
|
Object.entries(payloadParsed as object).filter(([_, v]) => v !== null),
|
||||||
|
)
|
||||||
|
|
||||||
|
const payload = jwtPayloadSchema.parse(payloadParsed)
|
||||||
|
const protectedHeader = result.protectedHeader
|
||||||
|
|
||||||
if (options?.audience != null) {
|
if (options?.audience != null) {
|
||||||
const audience = Array.isArray(options.audience)
|
const audience = Array.isArray(options.audience)
|
||||||
@@ -85,6 +89,8 @@ export class ReactNativeKey extends Key {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(payload)
|
||||||
|
|
||||||
if (payload.iat == null) {
|
if (payload.iat == null) {
|
||||||
throw new Error('Missing issued at')
|
throw new Error('Missing issued at')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,17 +64,11 @@ function HomeScreenReady({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sub: 'test',
|
sub: 'test',
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(jwt)
|
const verified = await key.verifyJwt(jwt)
|
||||||
|
|
||||||
// console.log('public', key.publicJwk)
|
|
||||||
// const jwt = await key.createJwt(
|
|
||||||
// {alg: 'ES256', kid: key.kid},
|
|
||||||
// {sub: 'test'},
|
|
||||||
// )
|
|
||||||
// console.log(jwt)
|
|
||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user