1
1
use poem:: session:: Session ;
2
- use poem:: web:: Data ;
2
+ use poem:: web:: { Data , Form } ;
3
3
use poem:: Request ;
4
4
use poem_openapi:: param:: Query ;
5
- use poem_openapi:: payload:: { Json , Response } ;
5
+ use poem_openapi:: payload:: { Html , Json , Response } ;
6
6
use poem_openapi:: { ApiResponse , Enum , Object , OpenApi } ;
7
+ use serde:: Deserialize ;
7
8
use tracing:: * ;
8
9
use warpgate_common:: auth:: { AuthCredential , AuthResult } ;
9
10
use warpgate_core:: Services ;
@@ -42,6 +43,23 @@ enum ReturnToSsoResponse {
42
43
Ok ,
43
44
}
44
45
46
+ #[ allow( clippy:: large_enum_variant) ]
47
+ #[ derive( ApiResponse ) ]
48
+ enum ReturnToSsoPostResponse {
49
+ #[ oai( status = 200 ) ]
50
+ Redirect ( Html < String > ) ,
51
+ }
52
+
53
+ #[ derive( Deserialize ) ]
54
+ pub struct ReturnToSsoFormData {
55
+ pub code : Option < String > ,
56
+ }
57
+
58
+ fn make_redirect_url ( err : & str ) -> String {
59
+ error ! ( "SSO error: {err}" ) ;
60
+ format ! ( "/@warpgate?login_error={err}" )
61
+ }
62
+
45
63
#[ OpenApi ]
46
64
impl Api {
47
65
#[ oai(
@@ -73,25 +91,68 @@ impl Api {
73
91
}
74
92
75
93
#[ oai( path = "/sso/return" , method = "get" , operation_id = "return_to_sso" ) ]
76
- async fn api_return_to_sso (
94
+ async fn api_return_to_sso_get (
77
95
& self ,
78
96
req : & Request ,
79
97
session : & Session ,
80
98
services : Data < & Services > ,
81
99
code : Query < Option < String > > ,
82
100
) -> poem:: Result < Response < ReturnToSsoResponse > > {
83
- fn make_err_response ( err : & str ) -> poem:: Result < Response < ReturnToSsoResponse > > {
84
- error ! ( "SSO error: {err}" ) ;
85
- Ok ( Response :: new ( ReturnToSsoResponse :: Ok )
86
- . header ( "Location" , format ! ( "/@warpgate?login_error={err}" ) ) )
87
- }
101
+ let url = self
102
+ . api_return_to_sso_get_common ( req, session, services, & * code)
103
+ . await ?
104
+ . unwrap_or_else ( |x| make_redirect_url ( & x) ) ;
105
+
106
+ Ok ( Response :: new ( ReturnToSsoResponse :: Ok ) . header ( "Location" , url) )
107
+ }
108
+
109
+ #[ oai(
110
+ path = "/sso/return" ,
111
+ method = "post" ,
112
+ operation_id = "return_to_sso_with_form_data"
113
+ ) ]
114
+ async fn api_return_to_sso_post (
115
+ & self ,
116
+ req : & Request ,
117
+ session : & Session ,
118
+ services : Data < & Services > ,
119
+ data : Form < ReturnToSsoFormData > ,
120
+ ) -> poem:: Result < ReturnToSsoPostResponse > {
121
+ let url = self
122
+ . api_return_to_sso_get_common ( req, session, services, & data. code )
123
+ . await ?
124
+ . unwrap_or_else ( |x| make_redirect_url ( & x) ) ;
125
+ let serialized_url =
126
+ serde_json:: to_string ( & url) . map_err ( poem:: error:: InternalServerError ) ?;
127
+ Ok ( ReturnToSsoPostResponse :: Redirect (
128
+ poem_openapi:: payload:: Html ( format ! (
129
+ "<!doctype html>\n
130
+ <html>
131
+ <script>
132
+ location.href = {serialized_url};
133
+ </script>
134
+ <body>
135
+ Redirecting to <a href='{url}'>{url}</a>...
136
+ </body>
137
+ </html>
138
+ "
139
+ ) ) ,
140
+ ) )
141
+ }
88
142
143
+ async fn api_return_to_sso_get_common (
144
+ & self ,
145
+ req : & Request ,
146
+ session : & Session ,
147
+ services : Data < & Services > ,
148
+ code : & Option < String > ,
149
+ ) -> poem:: Result < Result < String , String > > {
89
150
let Some ( context) = session. get :: < SsoContext > ( SSO_CONTEXT_SESSION_KEY ) else {
90
- return make_err_response ( "Not in an active SSO process" ) ;
151
+ return Ok ( Err ( "Not in an active SSO process" . to_string ( ) ) ) ;
91
152
} ;
92
153
93
154
let Some ( ref code) = * code else {
94
- return make_err_response ( "No authorization code in the return URL request" ) ;
155
+ return Ok ( Err ( "No authorization code in the return URL request" . to_string ( ) ) ) ;
95
156
} ;
96
157
97
158
let response = context
@@ -101,11 +162,11 @@ impl Api {
101
162
. map_err ( poem:: error:: InternalServerError ) ?;
102
163
103
164
if !response. email_verified . unwrap_or ( true ) {
104
- return make_err_response ( "The SSO account's e-mail is not verified" ) ;
165
+ return Ok ( Err ( "The SSO account's e-mail is not verified" . to_string ( ) ) ) ;
105
166
}
106
167
107
168
let Some ( email) = response. email else {
108
- return make_err_response ( "No e-mail information in the SSO response" ) ;
169
+ return Ok ( Err ( "No e-mail information in the SSO response" . to_string ( ) ) ) ;
109
170
} ;
110
171
111
172
info ! ( "SSO login as {email}" ) ;
@@ -122,7 +183,7 @@ impl Api {
122
183
. username_for_sso_credential ( & cred)
123
184
. await ?;
124
185
let Some ( username) = username else {
125
- return make_err_response ( & format ! ( "No user matching {email}" ) ) ;
186
+ return Ok ( Err ( format ! ( "No user matching {email}" ) ) ) ;
126
187
} ;
127
188
128
189
let mut auth_state_store = services. auth_state_store . lock ( ) . await ;
@@ -141,9 +202,10 @@ impl Api {
141
202
authorize_session ( req, username) . await ?;
142
203
}
143
204
144
- Ok ( Response :: new ( ReturnToSsoResponse :: Ok ) . header (
145
- "Location" ,
146
- context. next_url . as_deref ( ) . unwrap_or ( "/@warpgate#/login" ) ,
147
- ) )
205
+ Ok ( Ok ( context
206
+ . next_url
207
+ . as_deref ( )
208
+ . unwrap_or ( "/@warpgate#/login" )
209
+ . to_owned ( ) ) )
148
210
}
149
211
}
0 commit comments