tangled
alpha
login
or
join now
futur.blue
/
pegasus
56
fork
atom
objective categorical abstract machine language personal data server
56
fork
atom
overview
issues
2
pulls
pipelines
generate oauth code in get /oauth/authorize
futur.blue
4 months ago
34e13cf0
df46ef20
verified
This commit was signed with the committer's
known signature
.
futur.blue
SSH Key Fingerprint:
SHA256:QHGqHWNpqYyw9bt8KmPuJIyeZX9SZewBZ0PR1COtKQ0=
+131
-65
2 changed files
expand all
collapse all
unified
split
pegasus
lib
api
oauth_
authorize.ml
oauth
queries.ml
+105
-65
pegasus/lib/api/oauth_/authorize.ml
···
40
40
~msg:"failed to parse par request" () )
41
41
|> Result.get_ok
42
42
in
43
43
-
let%lwt client =
43
43
+
let%lwt _client =
44
44
try%lwt Oauth.Client.fetch_client_metadata client_id
45
45
with _ ->
46
46
Errors.internal_error
47
47
~msg:"failed to fetch client metadata" ()
48
48
in
49
49
+
let code =
50
50
+
"cod-"
51
51
+
^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ())
52
52
+
in
53
53
+
let expires_at =
54
54
+
Util.now_ms () + Oauth.Constants.code_expiry_ms
55
55
+
in
56
56
+
let%lwt () =
57
57
+
Oauth.Queries.insert_auth_code ctx.db
58
58
+
{ code
59
59
+
; request_id
60
60
+
; authorized_by= None
61
61
+
; authorized_at= None
62
62
+
; expires_at
63
63
+
; used= false }
64
64
+
in
49
65
match%lwt get_session_user ctx with
50
66
| None ->
51
67
Dream.redirect ctx.req ("/login?return_to=" ^ return_url)
···
54
70
| Some hint when hint <> did ->
55
71
Dream.redirect ctx.req ("/login?return_to=" ^ return_url)
56
72
| _ ->
73
73
+
(*
57
74
let%lwt handle =
58
75
match%lwt
59
76
Data_store.get_actor_by_identifier did ctx.db
···
72
89
| None ->
73
90
client_id
74
91
in
75
75
-
(* TODO: render authz page with client_name, handle, scopes, request_uri *)
92
92
+
[ ("client_name", `String client_name)
93
93
+
; ("handle", `String handle)
94
94
+
; ( "scopes"
95
95
+
, `List (List.map (fun s -> `String s) scopes) )
96
96
+
; ("code", `String code)
97
97
+
("request_uri", `String request_uri) ]
98
98
+
*)
76
99
Dream.html "" ) ) ) )
77
100
78
101
let post_handler pool =
···
84
107
match%lwt Dream.form ctx.req with
85
108
| `Ok fields -> (
86
109
let action = List.assoc_opt "action" fields in
110
110
+
let code = List.assoc_opt "code" fields in
87
111
let request_uri = List.assoc_opt "request_uri" fields in
88
88
-
if action <> Some "allow" || request_uri = None then
89
89
-
Errors.invalid_request "invalid request" ;
90
90
-
let request_uri = Option.get request_uri in
91
91
-
let prefix = Oauth.Constants.request_uri_prefix in
92
92
-
let request_id =
93
93
-
String.sub request_uri (String.length prefix)
94
94
-
(String.length request_uri - String.length prefix)
95
95
-
in
96
96
-
let%lwt stored_request =
97
97
-
Oauth.Queries.get_par_request pool request_id
98
98
-
in
99
99
-
match stored_request with
100
100
-
| None ->
101
101
-
Errors.invalid_request "request expired"
102
102
-
| Some req_record ->
103
103
-
let req =
104
104
-
Yojson.Safe.from_string req_record.request_data
105
105
-
|> par_request_of_yojson
106
106
-
|> Result.map_error (fun _ ->
107
107
-
Errors.internal_error
108
108
-
~msg:"failed to parse stored request" () )
109
109
-
|> Result.get_ok
112
112
+
match (action, code, request_uri) with
113
113
+
| Some "deny", _, Some request_uri -> (
114
114
+
let prefix = Oauth.Constants.request_uri_prefix in
115
115
+
let request_id =
116
116
+
String.sub request_uri (String.length prefix)
117
117
+
(String.length request_uri - String.length prefix)
110
118
in
111
111
-
if Util.now_ms () > req_record.expires_at then
112
112
-
Errors.invalid_request "request expired"
113
113
-
else
114
114
-
let code =
115
115
-
"cod-"
116
116
-
^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ())
117
117
-
in
118
118
-
let expires_at =
119
119
-
Util.now_ms () + Oauth.Constants.code_expiry_ms
120
120
-
in
121
121
-
let%lwt () =
122
122
-
Oauth.Queries.insert_auth_code pool
123
123
-
{ code
124
124
-
; request_id
125
125
-
; authorized_by= Some user_did
126
126
-
; authorized_at= Some (Util.now_ms ())
127
127
-
; expires_at
128
128
-
; used= false }
129
129
-
in
130
130
-
let params =
131
131
-
[ ("code", code)
132
132
-
; ("state", req.state)
133
133
-
; ("iss", "https://" ^ Env.hostname) ]
134
134
-
in
135
135
-
let query =
136
136
-
String.concat "&"
137
137
-
(List.map
138
138
-
(fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
139
139
-
params )
140
140
-
in
141
141
-
let separator =
142
142
-
match req.response_mode with
143
143
-
| Some "query" ->
144
144
-
"?"
145
145
-
| Some "fragment" ->
146
146
-
"#"
147
147
-
| _ ->
148
148
-
"?"
149
149
-
in
150
150
-
let redirect_uri = req.redirect_uri ^ separator ^ query in
151
151
-
Dream.redirect ctx.req redirect_uri )
119
119
+
let%lwt req_record =
120
120
+
Oauth.Queries.get_par_request pool request_id
121
121
+
in
122
122
+
match req_record with
123
123
+
| Some rec_ ->
124
124
+
let req =
125
125
+
Yojson.Safe.from_string rec_.request_data
126
126
+
|> par_request_of_yojson |> Result.get_ok
127
127
+
in
128
128
+
let params =
129
129
+
[ ("error", "access_denied")
130
130
+
; ("error_description", "Unable to authorize user.")
131
131
+
; ("state", req.state)
132
132
+
; ("iss", "https://" ^ Env.hostname) ]
133
133
+
in
134
134
+
let query =
135
135
+
String.concat "&"
136
136
+
(List.map
137
137
+
(fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
138
138
+
params )
139
139
+
in
140
140
+
Dream.redirect ctx.req (req.redirect_uri ^ "?" ^ query)
141
141
+
| None ->
142
142
+
Errors.invalid_request "request expired" )
143
143
+
| Some "allow", Some code, Some _request_uri -> (
144
144
+
let%lwt code_record = Oauth.Queries.get_auth_code pool code in
145
145
+
match code_record with
146
146
+
| None ->
147
147
+
Errors.invalid_request "invalid code"
148
148
+
| Some code_rec -> (
149
149
+
if code_rec.authorized_by <> None then
150
150
+
Errors.invalid_request "code already authorized"
151
151
+
else if code_rec.used then
152
152
+
Errors.invalid_request "code already used"
153
153
+
else if Util.now_ms () > code_rec.expires_at then
154
154
+
Errors.invalid_request "code expired"
155
155
+
else
156
156
+
let%lwt () =
157
157
+
Oauth.Queries.activate_auth_code pool code user_did
158
158
+
in
159
159
+
let%lwt req_record =
160
160
+
Oauth.Queries.get_par_request pool code_rec.request_id
161
161
+
in
162
162
+
match req_record with
163
163
+
| None ->
164
164
+
Errors.internal_error ~msg:"request not found" ()
165
165
+
| Some rec_ ->
166
166
+
let req =
167
167
+
Yojson.Safe.from_string rec_.request_data
168
168
+
|> par_request_of_yojson |> Result.get_ok
169
169
+
in
170
170
+
let params =
171
171
+
[ ("code", code)
172
172
+
; ("state", req.state)
173
173
+
; ("iss", "https://" ^ Env.hostname) ]
174
174
+
in
175
175
+
let query =
176
176
+
String.concat "&"
177
177
+
(List.map
178
178
+
(fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
179
179
+
params )
180
180
+
in
181
181
+
let separator =
182
182
+
match req.response_mode with
183
183
+
| Some "fragment" ->
184
184
+
"#"
185
185
+
| _ ->
186
186
+
"?"
187
187
+
in
188
188
+
Dream.redirect ctx.req
189
189
+
(req.redirect_uri ^ separator ^ query) ) )
190
190
+
| _ ->
191
191
+
Errors.invalid_request "invalid request" )
152
192
| _ ->
153
193
Errors.invalid_request "invalid request" ) )
+26
pegasus/lib/oauth/queries.ml
···
38
38
record_in]
39
39
code
40
40
41
41
+
let get_auth_code conn code =
42
42
+
Util.use_pool conn
43
43
+
@@ [%rapper
44
44
+
get_opt
45
45
+
{sql|
46
46
+
SELECT @string{code}, @string{request_id}, @string?{authorized_by},
47
47
+
@int?{authorized_at}, @int{expires_at}, @bool{used}
48
48
+
FROM oauth_codes
49
49
+
WHERE code = %string{code}
50
50
+
|sql}
51
51
+
record_out]
52
52
+
~code
53
53
+
54
54
+
let activate_auth_code conn code did =
55
55
+
let authorized_at = Util.now_ms () in
56
56
+
Util.use_pool conn
57
57
+
@@ [%rapper
58
58
+
execute
59
59
+
{sql|
60
60
+
UPDATE oauth_codes
61
61
+
SET authorized_by = %string{did},
62
62
+
authorized_at = %int{authorized_at}
63
63
+
WHERE code = %string{code} AND authorized_by = NULL
64
64
+
|sql}]
65
65
+
~did ~authorized_at ~code
66
66
+
41
67
let consume_auth_code conn code =
42
68
Util.use_pool conn
43
69
@@ [%rapper