@@ -46,6 +46,12 @@ public final class HTTPOAuthLibrary: NativeLibrary {
46
46
private let clientCredentialsReddit : Symbol
47
47
private let passwordGrant : Symbol
48
48
private let deviceGrant : Symbol
49
+ private let userCode : Symbol
50
+ private let verificationUrl : Symbol
51
+ private let verificationUrlComplete : Symbol
52
+ private let expiresIn : Symbol
53
+ private let interval : Symbol
54
+ private let deviceCode : Symbol
49
55
50
56
/// Initialize symbols.
51
57
public required init ( in context: Context ) throws {
@@ -62,6 +68,12 @@ public final class HTTPOAuthLibrary: NativeLibrary {
62
68
self . clientCredentialsReddit = context. symbols. intern ( " client-credentials-reddit " )
63
69
self . passwordGrant = context. symbols. intern ( " password-grant " )
64
70
self . deviceGrant = context. symbols. intern ( " device-grant " )
71
+ self . userCode = context. symbols. intern ( " user-code " )
72
+ self . verificationUrl = context. symbols. intern ( " verification-url " )
73
+ self . verificationUrlComplete = context. symbols. intern ( " verification-url-complete " )
74
+ self . expiresIn = context. symbols. intern ( " expires-in " )
75
+ self . interval = context. symbols. intern ( " interval " )
76
+ self . deviceCode = context. symbols. intern ( " device-code " )
65
77
try super. init ( in: context)
66
78
}
67
79
@@ -88,6 +100,7 @@ public final class HTTPOAuthLibrary: NativeLibrary {
88
100
self . define ( Procedure ( " oauth2-refresh-token " , self . oauth2RefreshToken) )
89
101
self . define ( Procedure ( " oauth2-forget-tokens! " , self . oauth2ForgetTokens) )
90
102
self . define ( Procedure ( " oauth2-cancel-requests! " , self . oauth2ForgetTokens) )
103
+ self . define ( Procedure ( " oauth2-request-codes " , self . oauth2RequestCodes) )
91
104
self . define ( Procedure ( " oauth2-authorize! " , self . oauth2Authorize) )
92
105
self . define ( Procedure ( " http-request-sign! " , self . httpRequestSign) )
93
106
self . define ( Procedure ( " oauth2-session? " , self . isOAuth2Session) )
@@ -161,7 +174,7 @@ public final class HTTPOAuthLibrary: NativeLibrary {
161
174
case self . passwordGrant:
162
175
oauth2 = OAuth2PasswordGrant ( settings: settings)
163
176
case self . deviceGrant:
164
- oauth2 = OAuth2DeviceGrant ( settings: settings)
177
+ oauth2 = OAuth2DeviceGrantLK ( settings: settings)
165
178
default :
166
179
throw RuntimeError . custom ( " error " , " unknown flow identifier " , [ . symbol( flow) ] )
167
180
}
@@ -476,6 +489,21 @@ public final class HTTPOAuthLibrary: NativeLibrary {
476
489
return settings
477
490
}
478
491
492
+ private func oauth2Params( from: Expr ? ) throws -> OAuth2StringDict ? {
493
+ guard var list = from else {
494
+ return nil
495
+ }
496
+ var dict : OAuth2StringDict = [ : ]
497
+ while case . pair( . pair( let key, let value) , let rest) = list {
498
+ dict [ try key. asString ( ) ] = try value. asString ( )
499
+ list = rest
500
+ }
501
+ guard case . null = list else {
502
+ throw RuntimeError . type ( from!, expected: [ . properListType] )
503
+ }
504
+ return dict
505
+ }
506
+
479
507
private func oauth2AccessToken( expr: Expr ) throws -> Expr {
480
508
if let token = try self . oauth2 ( from: expr) . oauth2. accessToken {
481
509
return . makeString( token)
@@ -533,7 +561,49 @@ public final class HTTPOAuthLibrary: NativeLibrary {
533
561
return res
534
562
}
535
563
536
- private func authorizeHandler( _ f: Future ) -> ( OAuth2JSON ? , OAuth2Error ? ) -> Void {
564
+ private func oauth2RequestCodes( expr: Expr , nonTextual: Expr ? , params: Expr ? ) throws -> Expr {
565
+ let oauth2 = try self . oauth2 ( from: expr)
566
+ guard let client = oauth2. oauth2 as? OAuth2DeviceGrantLK else {
567
+ throw RuntimeError . custom ( " error " , " expecting oauth2 client for the device-grant flow: " , [ expr] )
568
+ }
569
+ let params = params == nil ? [ : ] : try self . oauth2Params ( from: params!)
570
+ let f = Future ( external: false )
571
+ HTTPOAuthLibrary . authRequestManager. register ( oauth2: client, result: f, in: self . context)
572
+ client. start ( useNonTextualTransmission: nonTextual? . isTrue ?? false , params: params, queue: nil ) { codes, error in
573
+ defer {
574
+ HTTPOAuthLibrary . authRequestManager. unregister ( future: f, in: self . context)
575
+ }
576
+ do {
577
+ if let error {
578
+ _ = try f. setResult ( in: self . context, to: . error( RuntimeError . os ( error) ) , raise: true )
579
+ } else if let codes {
580
+ var res = Expr . null
581
+ res = . pair( . pair( . symbol( self . interval) , . makeNumber( codes. interval) ) , res)
582
+ res = . pair( . pair( . symbol( self . deviceCode) , . makeString( codes. deviceCode) ) , res)
583
+ if let url = codes. verificationUrlComplete {
584
+ res = . pair( . pair( . symbol( self . verificationUrlComplete) , . makeString( url. absoluteString) ) , res)
585
+ }
586
+ res = . pair( . pair( . symbol( self . verificationUrl) , . makeString( codes. verificationUrl. absoluteString) ) , res)
587
+ res = . pair( . pair( . symbol( self . expiresIn) , . makeNumber( codes. expiresIn) ) , res)
588
+ res = . pair( . pair( . symbol( self . userCode) , . makeString( codes. userCode) ) , res)
589
+ _ = try f. setResult ( in: self . context, to: res, raise: false )
590
+ } else {
591
+ _ = try f. setResult ( in: self . context,
592
+ to: . error( RuntimeError . eval ( . serverError) ) ,
593
+ raise: true )
594
+ }
595
+ } catch {
596
+ do {
597
+ _ = try f. setResult ( in: self . context,
598
+ to: . error( RuntimeError . eval ( . serverError, . object( f) ) ) ,
599
+ raise: true )
600
+ } catch { }
601
+ }
602
+ }
603
+ return . object( f)
604
+ }
605
+
606
+ private func authorizeHandler( _ f: Future ) -> ( OAuth2JSON ? , Error ? ) -> Void {
537
607
return { params, error in
538
608
defer {
539
609
HTTPOAuthLibrary . authRequestManager. unregister ( future: f, in: self . context)
@@ -562,7 +632,35 @@ public final class HTTPOAuthLibrary: NativeLibrary {
562
632
let oauth2 = try self . oauth2 ( from: expr)
563
633
let f = Future ( external: false )
564
634
HTTPOAuthLibrary . authRequestManager. register ( oauth2: oauth2. oauth2, result: f, in: self . context)
565
- oauth2. oauth2. authorize ( callback: self . authorizeHandler ( f) )
635
+ if let oauth2DeviceGrant = oauth2. oauth2 as? OAuth2DeviceGrantLK {
636
+ if oauth2DeviceGrant. hasUnexpiredAccessToken ( ) {
637
+ var params = Expr . null
638
+ params = . pair( . pair( . makeString( " token_type " ) , . makeString( " bearer " ) ) , params)
639
+ if let scope = oauth2DeviceGrant. scope {
640
+ params = . pair( . pair( . makeString( " scope " ) , . makeString( scope) ) , params)
641
+ }
642
+ if let accessToken = oauth2DeviceGrant. accessToken {
643
+ params = . pair( . pair( . makeString( " access_token " ) , . makeString( accessToken) ) , params)
644
+ }
645
+ _ = try f. setResult ( in: self . context, to: params, raise: false )
646
+ } else if let deviceCode = oauth2DeviceGrant. deviceCode {
647
+ let callback = self . authorizeHandler ( f)
648
+ oauth2DeviceGrant. getDeviceAccessToken ( deviceCode: deviceCode,
649
+ interval: oauth2DeviceGrant. pollingInterval,
650
+ queue: . global( qos: . default) ) { params, error in
651
+ if let params {
652
+ oauth2DeviceGrant. didAuthorize ( withParameters: params)
653
+ } else if let error {
654
+ oauth2DeviceGrant. didFail ( with: error. asOAuth2Error)
655
+ }
656
+ callback ( params, error)
657
+ }
658
+ } else {
659
+ throw RuntimeError . custom ( " error " , " OAuth2 device grant client did not yet receive device code: " , [ expr] )
660
+ }
661
+ } else {
662
+ oauth2. oauth2. authorize ( callback: self . authorizeHandler ( f) )
663
+ }
566
664
return . object( f)
567
665
}
568
666
0 commit comments