comparison mod_http_oauth2/mod_http_oauth2.lua @ 5653:62c6e17a5e9d

Merge
author Stephen Paul Weber <singpolyma@singpolyma.net>
date Mon, 18 Sep 2023 08:24:19 -0500
parents d67980d9e12d
children bbde136a4c29
comparison
equal deleted inserted replaced
5652:eade7ff9f52c 5653:62c6e17a5e9d
1 local usermanager = require "core.usermanager";
2 local url = require "socket.url";
3 local array = require "util.array";
4 local cache = require "util.cache";
5 local encodings = require "util.encodings";
6 local errors = require "util.error";
1 local hashes = require "util.hashes"; 7 local hashes = require "util.hashes";
2 local cache = require "util.cache";
3 local http = require "util.http"; 8 local http = require "util.http";
9 local id = require "util.id";
10 local it = require "util.iterators";
4 local jid = require "util.jid"; 11 local jid = require "util.jid";
5 local json = require "util.json"; 12 local json = require "util.json";
6 local usermanager = require "core.usermanager"; 13 local schema = require "util.jsonschema";
7 local errors = require "util.error"; 14 local jwt = require "util.jwt";
8 local url = require "socket.url"; 15 local random = require "util.random";
9 local id = require "util.id"; 16 local set = require "util.set";
10 local encodings = require "util.encodings"; 17 local st = require "util.stanza";
18
11 local base64 = encodings.base64; 19 local base64 = encodings.base64;
12 local random = require "util.random";
13 local schema = require "util.jsonschema";
14 local set = require "util.set";
15 local jwt = require"util.jwt";
16 local it = require "util.iterators";
17 local array = require "util.array";
18 local st = require "util.stanza";
19 20
20 local function b64url(s) 21 local function b64url(s)
21 return (base64.encode(s):gsub("[+/=]", { ["+"] = "-", ["/"] = "_", ["="] = "" })) 22 return (base64.encode(s):gsub("[+/=]", { ["+"] = "-", ["/"] = "_", ["="] = "" }))
22 end 23 end
23 24
24 local function tmap(t) 25 local function tmap(t)
25 return function(k) 26 return function(k)
26 return t[k]; 27 return t[k];
27 end 28 end
29 end
30
31 local function strict_formdecode(query)
32 if not query then
33 return nil;
34 end
35 local params = http.formdecode(query);
36 if type(params) ~= "table" then
37 return nil, "no-pairs";
38 end
39 local dups = {};
40 for _, pair in ipairs(params) do
41 if dups[pair.name] then
42 return nil, "duplicate";
43 end
44 dups[pair.name] = true;
45 end
46 return params;
28 end 47 end
29 48
30 local function read_file(base_path, fn, required) 49 local function read_file(base_path, fn, required)
31 local f, err = io.open(base_path .. "/" .. fn); 50 local f, err = io.open(base_path .. "/" .. fn);
32 if not f then 51 if not f then
39 local data = assert(f:read("*a")); 58 local data = assert(f:read("*a"));
40 assert(f:close()); 59 assert(f:close());
41 return data; 60 return data;
42 end 61 end
43 62
63 local allowed_locales = module:get_option_array("allowed_oauth2_locales", {});
64 -- TODO Allow translations or per-locale templates somehow.
65
44 local template_path = module:get_option_path("oauth2_template_path", "html"); 66 local template_path = module:get_option_path("oauth2_template_path", "html");
45 local templates = { 67 local templates = {
46 login = read_file(template_path, "login.html", true); 68 login = read_file(template_path, "login.html", true);
47 consent = read_file(template_path, "consent.html", true); 69 consent = read_file(template_path, "consent.html", true);
70 oob = read_file(template_path, "oob.html", true);
71 device = read_file(template_path, "device.html", true);
48 error = read_file(template_path, "error.html", true); 72 error = read_file(template_path, "error.html", true);
49 css = read_file(template_path, "style.css"); 73 css = read_file(template_path, "style.css");
50 js = read_file(template_path, "script.js"); 74 js = read_file(template_path, "script.js");
51 }; 75 };
52 76
53 local site_name = module:get_option_string("site_name", module.host); 77 local site_name = module:get_option_string("site_name", module.host);
54 78
55 local _render_html = require"util.interpolation".new("%b{}", st.xml_escape); 79 local security_policy = module:get_option_string("oauth2_security_policy", "default-src 'self'");
80
81 local render_html = require"util.interpolation".new("%b{}", st.xml_escape);
56 local function render_page(template, data, sensitive) 82 local function render_page(template, data, sensitive)
57 data = data or {}; 83 data = data or {};
58 data.site_name = site_name; 84 data.site_name = site_name;
59 local resp = { 85 local resp = {
60 status_code = 200; 86 status_code = data.error and data.error.code or 200;
61 headers = { 87 headers = {
62 ["Content-Type"] = "text/html; charset=utf-8"; 88 ["Content-Type"] = "text/html; charset=utf-8";
63 ["Content-Security-Policy"] = "default-src 'self'"; 89 ["Content-Security-Policy"] = security_policy;
90 ["Referrer-Policy"] = "no-referrer";
64 ["X-Frame-Options"] = "DENY"; 91 ["X-Frame-Options"] = "DENY";
65 ["Cache-Control"] = (sensitive and "no-store" or "no-cache")..", private"; 92 ["Cache-Control"] = (sensitive and "no-store" or "no-cache")..", private";
66 }; 93 ["Pragma"] = "no-cache";
67 body = _render_html(template, data); 94 };
95 body = render_html(template, data);
68 }; 96 };
69 return resp; 97 return resp;
70 end 98 end
71 99
100 local authorization_server_metadata = nil;
101
72 local tokens = module:depends("tokenauth"); 102 local tokens = module:depends("tokenauth");
73 103
74 local default_access_ttl = module:get_option_number("oauth2_access_token_ttl", 86400); 104 local default_access_ttl = module:get_option_number("oauth2_access_token_ttl", 3600);
75 local default_refresh_ttl = module:get_option_number("oauth2_refresh_token_ttl", nil); 105 local default_refresh_ttl = module:get_option_number("oauth2_refresh_token_ttl", 604800);
76 106
77 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration. 107 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration.
78 local registration_key = module:get_option_string("oauth2_registration_key"); 108 local registration_key = module:get_option_string("oauth2_registration_key");
79 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256"); 109 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256");
80 local registration_ttl = module:get_option("oauth2_registration_ttl", nil); 110 local registration_ttl = module:get_option("oauth2_registration_ttl", nil);
82 { default_ttl = registration_ttl; accept_expired = not registration_ttl }); 112 { default_ttl = registration_ttl; accept_expired = not registration_ttl });
83 113
84 local pkce_required = module:get_option_boolean("oauth2_require_code_challenge", false); 114 local pkce_required = module:get_option_boolean("oauth2_require_code_challenge", false);
85 115
86 local verification_key; 116 local verification_key;
87 local jwt_sign, jwt_verify; 117 local sign_client, verify_client;
88 if registration_key then 118 if registration_key then
89 -- Tie it to the host if global 119 -- Tie it to the host if global
90 verification_key = hashes.hmac_sha256(registration_key, module.host); 120 verification_key = hashes.hmac_sha256(registration_key, module.host);
91 jwt_sign, jwt_verify = jwt.init(registration_algo, registration_key, registration_key, registration_options); 121 sign_client, verify_client = jwt.init(registration_algo, registration_key, registration_key, registration_options);
92 end 122 end
93 123
124 local new_device_token, verify_device_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 });
125
126 -- verify and prepare client structure
127 local function check_client(client_id)
128 if not verify_client then
129 return nil, "client-registration-not-enabled";
130 end
131
132 local ok, client = verify_client(client_id);
133 if not ok then
134 return ok, client;
135 end
136
137 client.client_hash = b64url(hashes.sha256(client_id));
138 return client;
139 end
140
141 -- scope : string | array | set
142 --
143 -- at each step, allow the same or a subset of scopes
144 -- (all ( client ( grant ( token ) ) ))
145 -- preserve order since it determines role if more than one granted
146
147 -- string -> array
94 local function parse_scopes(scope_string) 148 local function parse_scopes(scope_string)
95 return array(scope_string:gmatch("%S+")); 149 return array(scope_string:gmatch("%S+"));
96 end 150 end
97 151
98 local openid_claims = set.new({ "openid", "profile"; "email"; "address"; "phone" }); 152 local openid_claims = set.new();
99 153 module:add_item("openid-claim", "openid");
154
155 module:handle_items("openid-claim", function(event)
156 authorization_server_metadata = nil;
157 openid_claims:add(event.item);
158 end, function()
159 authorization_server_metadata = nil;
160 openid_claims = set.new(module:get_host_items("openid-claim"));
161 end, true);
162
163 -- array -> array, array, array
100 local function split_scopes(scope_list) 164 local function split_scopes(scope_list)
101 local claims, roles, unknown = array(), array(), array(); 165 local claims, roles, unknown = array(), array(), array();
102 local all_roles = usermanager.get_all_roles(module.host); 166 local all_roles = usermanager.get_all_roles(module.host);
103 for _, scope in ipairs(scope_list) do 167 for _, scope in ipairs(scope_list) do
104 if openid_claims:contains(scope) then 168 if openid_claims:contains(scope) then
105 claims:push(scope); 169 claims:push(scope);
106 elseif all_roles[scope] then 170 elseif scope == "xmpp" or all_roles[scope] then
107 roles:push(scope); 171 roles:push(scope);
108 else 172 else
109 unknown:push(scope); 173 unknown:push(scope);
110 end 174 end
111 end 175 end
112 return claims, roles, unknown; 176 return claims, roles, unknown;
113 end 177 end
114 178
115 local function can_assume_role(username, requested_role) 179 local function can_assume_role(username, requested_role)
116 return usermanager.user_can_assume_role(username, module.host, requested_role); 180 return requested_role == "xmpp" or usermanager.user_can_assume_role(username, module.host, requested_role);
117 end 181 end
118 182
119 local function select_role(username, requested_roles) 183 -- function (string) : function(string) : boolean
120 if requested_roles then 184 local function role_assumable_by(username)
121 for _, requested_role in ipairs(requested_roles) do 185 return function(role)
122 if can_assume_role(username, requested_role) then 186 return can_assume_role(username, role);
123 return requested_role; 187 end
124 end 188 end
125 end 189
126 end 190 -- string, array --> array
127 -- otherwise the default role 191 local function user_assumable_roles(username, requested_roles)
128 return usermanager.get_user_role(username, module.host).name; 192 return array.filter(requested_roles, role_assumable_by(username));
129 end 193 end
130 194
195 -- string, string|nil --> string, string
131 local function filter_scopes(username, requested_scope_string) 196 local function filter_scopes(username, requested_scope_string)
132 local granted_scopes, requested_roles; 197 local requested_scopes, requested_roles = split_scopes(parse_scopes(requested_scope_string or ""));
133 198
134 if requested_scope_string then -- Specific role(s) requested 199 local granted_roles = user_assumable_roles(username, requested_roles);
135 granted_scopes, requested_roles = split_scopes(parse_scopes(requested_scope_string)); 200 local granted_scopes = requested_scopes + granted_roles;
136 else 201
137 granted_scopes = array(); 202 local selected_role = granted_roles[1];
138 end
139
140 local selected_role = select_role(username, requested_roles);
141 granted_scopes:push(selected_role);
142 203
143 return granted_scopes:concat(" "), selected_role; 204 return granted_scopes:concat(" "), selected_role;
144 end 205 end
145 206
146 local function code_expires_in(code) --> number, seconds until code expires 207 local function code_expires_in(code) --> number, seconds until code expires
153 214
154 local codes = cache.new(10000, function (_, code) 215 local codes = cache.new(10000, function (_, code)
155 return code_expired(code) 216 return code_expired(code)
156 end); 217 end);
157 218
158 -- Periodically clear out unredeemed codes. Does not need to be exact, expired 219 -- Clear out unredeemed codes so they don't linger in memory.
159 -- codes are rejected if tried. Mostly just to keep memory usage in check. 220 module:daily("Clear expired authorization codes", function()
160 module:hourly("Clear expired authorization codes", function()
161 local k, code = codes:tail(); 221 local k, code = codes:tail();
162 while code and code_expired(code) do 222 while code and code_expired(code) do
163 codes:set(k, nil); 223 codes:set(k, nil);
164 k, code = codes:tail(); 224 k, code = codes:tail();
165 end 225 end
167 227
168 local function get_issuer() 228 local function get_issuer()
169 return (module:http_url(nil, "/"):gsub("/$", "")); 229 return (module:http_url(nil, "/"):gsub("/$", ""));
170 end 230 end
171 231
232 -- Non-standard special redirect URI that has the AS show the authorization
233 -- code to the user for them to copy-paste into the client, which can then
234 -- continue as if it received it via redirect.
235 local oob_uri = "urn:ietf:wg:oauth:2.0:oob";
236 local device_uri = "urn:ietf:params:oauth:grant-type:device_code";
237
172 local loopbacks = set.new({ "localhost", "127.0.0.1", "::1" }); 238 local loopbacks = set.new({ "localhost", "127.0.0.1", "::1" });
173 local function is_secure_redirect(uri)
174 local u = url.parse(uri);
175 return u.scheme ~= "http" or loopbacks:contains(u.host);
176 end
177 239
178 local function oauth_error(err_name, err_desc) 240 local function oauth_error(err_name, err_desc)
179 return errors.new({ 241 return errors.new({
180 type = "modify"; 242 type = "modify";
181 condition = "bad-request"; 243 condition = "bad-request";
187 249
188 -- client_id / client_metadata are pretty large, filter out a subset of 250 -- client_id / client_metadata are pretty large, filter out a subset of
189 -- properties that are deemed useful e.g. in case tokens issued to a certain 251 -- properties that are deemed useful e.g. in case tokens issued to a certain
190 -- client needs to be revoked 252 -- client needs to be revoked
191 local function client_subset(client) 253 local function client_subset(client)
192 return { name = client.client_name; uri = client.client_uri; id = client.software_id; version = client.software_version }; 254 return {
255 name = client.client_name;
256 uri = client.client_uri;
257 id = client.software_id;
258 version = client.software_version;
259 hash = client.client_hash;
260 };
193 end 261 end
194 262
195 local function new_access_token(token_jid, role, scope_string, client, id_token, refresh_token_info) 263 local function new_access_token(token_jid, role, scope_string, client, id_token, refresh_token_info)
196 local token_data = { oauth2_scopes = scope_string, oauth2_client = nil }; 264 local token_data = { oauth2_scopes = scope_string, oauth2_client = nil };
197 if client then 265 if client then
199 end 267 end
200 if next(token_data) == nil then 268 if next(token_data) == nil then
201 token_data = nil; 269 token_data = nil;
202 end 270 end
203 271
204 local refresh_token;
205 local grant = refresh_token_info and refresh_token_info.grant; 272 local grant = refresh_token_info and refresh_token_info.grant;
206 if not grant then 273 if not grant then
207 -- No existing grant, create one 274 -- No existing grant, create one
208 grant = tokens.create_grant(token_jid, token_jid, default_refresh_ttl, token_data); 275 grant = tokens.create_grant(token_jid, token_jid, nil, token_data);
209 -- Create refresh token for the grant if desired 276 end
210 refresh_token = refresh_token_info ~= false and tokens.create_token(token_jid, grant, nil, nil, "oauth2-refresh"); 277
211 else 278 if refresh_token_info then
212 -- Grant exists, reuse existing refresh token 279 -- out with the old refresh tokens
213 refresh_token = refresh_token_info.token; 280 local ok, err = tokens.revoke_token(refresh_token_info.token);
214 281 if not ok then
215 refresh_token_info.grant = nil; -- Prevent reference loop 282 module:log("error", "Could not revoke refresh token: %s", err);
216 end 283 return 500;
217 284 end
218 local access_token, access_token_info = tokens.create_token(token_jid, grant, role, default_access_ttl, "oauth2"); 285 end
286 -- in with the new refresh token
287 local refresh_token = refresh_token_info ~= false and tokens.create_token(token_jid, grant.id, nil, default_refresh_ttl, "oauth2-refresh");
288
289 if role == "xmpp" then
290 -- Special scope meaning the users default role.
291 local user_default_role = usermanager.get_user_role(jid.node(token_jid), module.host);
292 role = user_default_role and user_default_role.name;
293 end
294
295 local access_token, access_token_info = tokens.create_token(token_jid, grant.id, role, default_access_ttl, "oauth2");
219 296
220 local expires_at = access_token_info.expires; 297 local expires_at = access_token_info.expires;
221 return { 298 return {
222 token_type = "bearer"; 299 token_type = "bearer";
223 access_token = access_token; 300 access_token = access_token;
226 id_token = id_token; 303 id_token = id_token;
227 refresh_token = refresh_token or nil; 304 refresh_token = refresh_token or nil;
228 }; 305 };
229 end 306 end
230 307
308 local function normalize_loopback(uri)
309 local u = url.parse(uri);
310 if u.scheme == "http" and loopbacks:contains(u.host) then
311 u.authority = nil;
312 u.host = "::1";
313 u.port = nil;
314 return url.build(u);
315 end
316 -- else, not a valid loopback uri
317 end
318
231 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string 319 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string
232 if not query_redirect_uri then 320 if not query_redirect_uri then
233 if #client.redirect_uris ~= 1 then 321 if #client.redirect_uris ~= 1 then
234 -- Client registered multiple URIs, it needs specify which one to use 322 -- Client registered multiple URIs, it needs specify which one to use
235 return; 323 return;
236 end 324 end
237 -- When only a single URI is registered, that's the default 325 -- When only a single URI is registered, that's the default
238 return client.redirect_uris[1]; 326 return client.redirect_uris[1];
239 end 327 end
328 if query_redirect_uri == device_uri and client.grant_types then
329 for _, grant_type in ipairs(client.grant_types) do
330 if grant_type == device_uri then
331 return query_redirect_uri;
332 end
333 end
334 -- Tried to use device authorization flow without registering it.
335 return;
336 end
240 -- Verify the client-provided URI matches one previously registered 337 -- Verify the client-provided URI matches one previously registered
241 for _, redirect_uri in ipairs(client.redirect_uris) do 338 for _, redirect_uri in ipairs(client.redirect_uris) do
242 if query_redirect_uri == redirect_uri then 339 if query_redirect_uri == redirect_uri then
243 return redirect_uri 340 return redirect_uri
244 end 341 end
245 end 342 end
343 -- The authorization server MUST allow any port to be specified at the time
344 -- of the request for loopback IP redirect URIs, to accommodate clients that
345 -- obtain an available ephemeral port from the operating system at the time
346 -- of the request.
347 -- https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-08.html#section-8.4.2
348 local loopback_redirect_uri = normalize_loopback(query_redirect_uri);
349 if loopback_redirect_uri then
350 for _, redirect_uri in ipairs(client.redirect_uris) do
351 if loopback_redirect_uri == normalize_loopback(redirect_uri) then
352 return query_redirect_uri;
353 end
354 end
355 end
246 end 356 end
247 357
248 local grant_type_handlers = {}; 358 local grant_type_handlers = {};
249 local response_type_handlers = {}; 359 local response_type_handlers = {};
250 local verifier_transforms = {}; 360 local verifier_transforms = {};
361
362 function grant_type_handlers.implicit()
363 -- Placeholder to make discovery work correctly.
364 -- Access tokens are delivered via redirect when using the implict flow, not
365 -- via the token endpoint, so how did you get here?
366 return oauth_error("invalid_request");
367 end
251 368
252 function grant_type_handlers.password(params) 369 function grant_type_handlers.password(params)
253 local request_jid = assert(params.username, oauth_error("invalid_request", "missing 'username' (JID)")); 370 local request_jid = assert(params.username, oauth_error("invalid_request", "missing 'username' (JID)"));
254 local request_password = assert(params.password, oauth_error("invalid_request", "missing 'password'")); 371 local request_password = assert(params.password, oauth_error("invalid_request", "missing 'password'"));
255 local request_username, request_host, request_resource = jid.prepped_split(request_jid); 372 local request_username, request_host, request_resource = jid.prepped_split(request_jid);
275 392
276 if pkce_required and not params.code_challenge then 393 if pkce_required and not params.code_challenge then
277 return oauth_error("invalid_request", "PKCE required"); 394 return oauth_error("invalid_request", "PKCE required");
278 end 395 end
279 396
397 local prefix = "authorization_code:";
280 local code = id.medium(); 398 local code = id.medium();
281 local ok = codes:set(params.client_id .. "#" .. code, { 399 if params.redirect_uri == device_uri then
400 local is_device, device_state = verify_device_token(params.state);
401 if is_device then
402 -- reconstruct the device_code
403 prefix = "device_code:";
404 code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code));
405 else
406 return oauth_error("invalid_request");
407 end
408 end
409 local ok = codes:set(prefix.. params.client_id .. "#" .. code, {
282 expires = os.time() + 600; 410 expires = os.time() + 600;
283 granted_jid = granted_jid; 411 granted_jid = granted_jid;
284 granted_scopes = granted_scopes; 412 granted_scopes = granted_scopes;
285 granted_role = granted_role; 413 granted_role = granted_role;
286 challenge = params.code_challenge; 414 challenge = params.code_challenge;
287 challenge_method = params.code_challenge_method; 415 challenge_method = params.code_challenge_method;
288 id_token = id_token; 416 id_token = id_token;
289 }); 417 });
290 if not ok then 418 if not ok then
291 return {status_code = 429}; 419 return oauth_error("temporarily_unavailable");
292 end 420 end
293 421
294 local redirect_uri = get_redirect_uri(client, params.redirect_uri); 422 local redirect_uri = get_redirect_uri(client, params.redirect_uri);
295 if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" then 423 if redirect_uri == oob_uri then
296 -- TODO some nicer template page 424 return render_page(templates.oob, { client = client; authorization_code = code }, true);
297 -- mod_http_errors will set content-type to text/html if it catches this 425 elseif redirect_uri == device_uri then
298 -- event, if not text/plain is kept for the fallback text. 426 return render_page(templates.device, { client = client }, true);
299 local response = { status_code = 200; headers = { content_type = "text/plain" } }
300 response.body = module:context("*"):fire_event("http-message", {
301 response = response;
302 title = "Your authorization code";
303 message = "Here's your authorization code, copy and paste it into " .. (client.client_name or "your client");
304 extra = code;
305 }) or ("Here's your authorization code:\n%s\n"):format(code);
306 return response;
307 elseif not redirect_uri then 427 elseif not redirect_uri then
308 return 400; 428 return oauth_error("invalid_redirect_uri");
309 end 429 end
310 430
311 local redirect = url.parse(redirect_uri); 431 local redirect = url.parse(redirect_uri);
312 432
313 local query = http.formdecode(redirect.query or ""); 433 local query = strict_formdecode(redirect.query);
314 if type(query) ~= "table" then query = {}; end 434 if type(query) ~= "table" then query = {}; end
315 table.insert(query, { name = "code", value = code }); 435 table.insert(query, { name = "code", value = code });
316 table.insert(query, { name = "iss", value = get_issuer() }); 436 table.insert(query, { name = "iss", value = get_issuer() });
317 if params.state then 437 if params.state then
318 table.insert(query, { name = "state", value = params.state }); 438 table.insert(query, { name = "state", value = params.state });
320 redirect.query = http.formencode(query); 440 redirect.query = http.formencode(query);
321 441
322 return { 442 return {
323 status_code = 303; 443 status_code = 303;
324 headers = { 444 headers = {
445 cache_control = "no-store";
446 pragma = "no-cache";
325 location = url.build(redirect); 447 location = url.build(redirect);
326 }; 448 };
327 } 449 }
328 end 450 end
329 451
335 end 457 end
336 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); 458 local granted_scopes, granted_role = filter_scopes(request_username, params.scope);
337 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, client, nil); 459 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, client, nil);
338 460
339 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri)); 461 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri));
340 if not redirect then return 400; end 462 if not redirect then return oauth_error("invalid_redirect_uri"); end
341 token_info.state = params.state; 463 token_info.state = params.state;
342 redirect.fragment = http.formencode(token_info); 464 redirect.fragment = http.formencode(token_info);
343 465
344 return { 466 return {
345 status_code = 303; 467 status_code = 303;
346 headers = { 468 headers = {
469 cache_control = "no-store";
470 pragma = "no-cache";
347 location = url.build(redirect); 471 location = url.build(redirect);
348 }; 472 };
349 } 473 }
350 end 474 end
351 475
360 function grant_type_handlers.authorization_code(params) 484 function grant_type_handlers.authorization_code(params)
361 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end 485 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end
362 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end 486 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end
363 if not params.code then return oauth_error("invalid_request", "missing 'code'"); end 487 if not params.code then return oauth_error("invalid_request", "missing 'code'"); end
364 if params.scope and params.scope ~= "" then 488 if params.scope and params.scope ~= "" then
489 -- FIXME allow a subset of granted scopes
365 return oauth_error("invalid_scope", "unknown scope requested"); 490 return oauth_error("invalid_scope", "unknown scope requested");
366 end 491 end
367 492
368 local client_ok, client = jwt_verify(params.client_id); 493 local client = check_client(params.client_id);
369 if not client_ok then 494 if not client then
370 return oauth_error("invalid_client", "incorrect credentials"); 495 return oauth_error("invalid_client", "incorrect credentials");
371 end 496 end
372 497
373 if not verify_client_secret(params.client_id, params.client_secret) then 498 if not verify_client_secret(params.client_id, params.client_secret) then
374 module:log("debug", "client_secret mismatch"); 499 module:log("debug", "client_secret mismatch");
375 return oauth_error("invalid_client", "incorrect credentials"); 500 return oauth_error("invalid_client", "incorrect credentials");
376 end 501 end
377 local code, err = codes:get(params.client_id .. "#" .. params.code); 502 local code, err = codes:get("authorization_code:" .. params.client_id .. "#" .. params.code);
378 if err then error(err); end 503 if err then error(err); end
379 -- MUST NOT use the authorization code more than once, so remove it to 504 -- MUST NOT use the authorization code more than once, so remove it to
380 -- prevent a second attempted use 505 -- prevent a second attempted use
381 codes:set(params.client_id .. "#" .. params.code, nil); 506 -- TODO if a second attempt *is* made, revoke any tokens issued
507 codes:set("authorization_code:" .. params.client_id .. "#" .. params.code, nil);
382 if not code or type(code) ~= "table" or code_expired(code) then 508 if not code or type(code) ~= "table" or code_expired(code) then
383 module:log("debug", "authorization_code invalid or expired: %q", code); 509 module:log("debug", "authorization_code invalid or expired: %q", code);
384 return oauth_error("invalid_client", "incorrect credentials"); 510 return oauth_error("invalid_client", "incorrect credentials");
385 end 511 end
386 512
398 function grant_type_handlers.refresh_token(params) 524 function grant_type_handlers.refresh_token(params)
399 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end 525 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end
400 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end 526 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end
401 if not params.refresh_token then return oauth_error("invalid_request", "missing 'refresh_token'"); end 527 if not params.refresh_token then return oauth_error("invalid_request", "missing 'refresh_token'"); end
402 528
403 local client_ok, client = jwt_verify(params.client_id); 529 local client = check_client(params.client_id);
404 if not client_ok then 530 if not client then
405 return oauth_error("invalid_client", "incorrect credentials"); 531 return oauth_error("invalid_client", "incorrect credentials");
406 end 532 end
407 533
408 if not verify_client_secret(params.client_id, params.client_secret) then 534 if not verify_client_secret(params.client_id, params.client_secret) then
409 module:log("debug", "client_secret mismatch"); 535 module:log("debug", "client_secret mismatch");
413 local refresh_token_info = tokens.get_token_info(params.refresh_token); 539 local refresh_token_info = tokens.get_token_info(params.refresh_token);
414 if not refresh_token_info or refresh_token_info.purpose ~= "oauth2-refresh" then 540 if not refresh_token_info or refresh_token_info.purpose ~= "oauth2-refresh" then
415 return oauth_error("invalid_grant", "invalid refresh token"); 541 return oauth_error("invalid_grant", "invalid refresh token");
416 end 542 end
417 543
544 local refresh_token_client = refresh_token_info.grant.data.oauth2_client;
545 if not refresh_token_client.hash or refresh_token_client.hash ~= client.client_hash then
546 module:log("warn", "OAuth client %q (%s) tried to use refresh token belonging to %q (%s)", client.client_name, client.client_hash,
547 refresh_token_client.name, refresh_token_client.hash);
548 return oauth_error("unauthorized_client", "incorrect credentials");
549 end
550
551 local refresh_scopes = refresh_token_info.grant.data.oauth2_scopes;
552
553 if params.scope then
554 local granted_scopes = set.new(parse_scopes(refresh_scopes));
555 local requested_scopes = parse_scopes(params.scope);
556 refresh_scopes = array.filter(requested_scopes, function(scope)
557 return granted_scopes:contains(scope);
558 end):concat(" ");
559 end
560
561 local username = jid.split(refresh_token_info.jid);
562 local new_scopes, role = filter_scopes(username, refresh_scopes);
563
418 -- new_access_token() requires the actual token 564 -- new_access_token() requires the actual token
419 refresh_token_info.token = params.refresh_token; 565 refresh_token_info.token = params.refresh_token;
420 566
421 return json.encode(new_access_token( 567 return json.encode(new_access_token(refresh_token_info.jid, role, new_scopes, client, nil, refresh_token_info));
422 refresh_token_info.jid, refresh_token_info.role, refresh_token_info.grant.data.oauth2_scopes, client, nil, refresh_token_info 568 end
423 )); 569
570 grant_type_handlers[device_uri] = function(params)
571 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end
572 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end
573 if not params.device_code then return oauth_error("invalid_request", "missing 'device_code'"); end
574
575 local client = check_client(params.client_id);
576 if not client then
577 return oauth_error("invalid_client", "incorrect credentials");
578 end
579
580 if not verify_client_secret(params.client_id, params.client_secret) then
581 module:log("debug", "client_secret mismatch");
582 return oauth_error("invalid_client", "incorrect credentials");
583 end
584
585 local code = codes:get("device_code:" .. params.client_id .. "#" .. params.device_code);
586 if type(code) ~= "table" or code_expired(code) then
587 return oauth_error("expired_token");
588 elseif code.error then
589 return code.error;
590 elseif not code.granted_jid then
591 return oauth_error("authorization_pending");
592 end
593 codes:set("device_code:" .. params.client_id .. "#" .. params.device_code, nil);
594
595 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token));
424 end 596 end
425 597
426 -- RFC 7636 Proof Key for Code Exchange by OAuth Public Clients 598 -- RFC 7636 Proof Key for Code Exchange by OAuth Public Clients
427 599
428 function verifier_transforms.plain(code_verifier) 600 function verifier_transforms.plain(code_verifier)
465 end 637 end
466 return { 638 return {
467 user = { 639 user = {
468 username = username; 640 username = username;
469 host = module.host; 641 host = module.host;
470 token = new_user_token({ username = username, host = module.host }); 642 token = new_user_token({ username = username; host = module.host; auth_time = os.time() });
471 }; 643 };
472 }; 644 };
473 elseif form.user_token and form.consent then 645 elseif form.user_token and form.consent then
474 -- Second step: consent 646 -- Second step: consent
475 local ok, user = verify_user_token(form.user_token); 647 local ok, user = verify_user_token(form.user_token);
477 return { 649 return {
478 error = user == "token-expired" and "Session expired - try again" or nil; 650 error = user == "token-expired" and "Session expired - try again" or nil;
479 }; 651 };
480 end 652 end
481 653
482 local scope = array():append(form):filter(function(field) 654 local scopes = array():append(form):filter(function(field)
483 return field.name == "scope" or field.name == "role"; 655 return field.name == "scope";
484 end):pluck("value"):concat(" "); 656 end):pluck("value");
485 657
486 user.token = form.user_token; 658 user.token = form.user_token;
487 return { 659 return {
488 user = user; 660 user = user;
489 scope = scope; 661 scopes = scopes;
490 consent = form.consent == "granted"; 662 consent = form.consent == "granted";
491 }; 663 };
492 end 664 end
493 665
494 return {}; 666 return {};
525 function grant_type_handlers.password(params) 697 function grant_type_handlers.password(params)
526 local request_jid = assert(params.username, oauth_error("invalid_request", "missing 'username' (JID)")); 698 local request_jid = assert(params.username, oauth_error("invalid_request", "missing 'username' (JID)"));
527 local request_password = assert(params.password, oauth_error("invalid_request", "missing 'password'")); 699 local request_password = assert(params.password, oauth_error("invalid_request", "missing 'password'"));
528 local request_username, request_host, request_resource = jid.prepped_split(request_jid); 700 local request_username, request_host, request_resource = jid.prepped_split(request_jid);
529 if params.scope then 701 if params.scope then
702 -- TODO shouldn't we support scopes / roles here?
530 return oauth_error("invalid_scope", "unknown scope requested"); 703 return oauth_error("invalid_scope", "unknown scope requested");
531 end 704 end
532 if not request_host or request_host ~= module.host then 705 if not request_host or request_host ~= module.host then
533 return oauth_error("invalid_request", "invalid JID"); 706 return oauth_error("invalid_request", "invalid JID");
534 end 707 end
544 response_type_handlers.code = nil; 717 response_type_handlers.code = nil;
545 response_type_handlers.token = nil; 718 response_type_handlers.token = nil;
546 grant_type_handlers.authorization_code = nil; 719 grant_type_handlers.authorization_code = nil;
547 end 720 end
548 721
722 local function render_error(err)
723 return render_page(templates.error, { error = err });
724 end
725
549 -- OAuth errors should be returned to the client if possible, i.e. by 726 -- OAuth errors should be returned to the client if possible, i.e. by
550 -- appending the error information to the redirect_uri and sending the 727 -- appending the error information to the redirect_uri and sending the
551 -- redirect to the user-agent. In some cases we can't do this, e.g. if 728 -- redirect to the user-agent. In some cases we can't do this, e.g. if
552 -- the redirect_uri is missing or invalid. In those cases, we render an 729 -- the redirect_uri is missing or invalid. In those cases, we render an
553 -- error directly to the user-agent. 730 -- error directly to the user-agent.
554 local function error_response(request, err) 731 local function error_response(request, redirect_uri, err)
555 local q = request.url.query and http.formdecode(request.url.query); 732 if not redirect_uri or redirect_uri == oob_uri then
556 local redirect_uri = q and q.redirect_uri; 733 return render_error(err);
557 if not redirect_uri or not is_secure_redirect(redirect_uri) then 734 end
558 module:log("warn", "Missing or invalid redirect_uri <%s>, rendering error to user-agent", redirect_uri or ""); 735 local q = strict_formdecode(request.url.query);
559 return render_page(templates.error, { error = err });
560 end
561 local redirect_query = url.parse(redirect_uri); 736 local redirect_query = url.parse(redirect_uri);
562 local sep = redirect_query.query and "&" or "?"; 737 local sep = redirect_query.query and "&" or "?";
563 redirect_uri = redirect_uri 738 redirect_uri = redirect_uri
564 .. sep .. http.formencode(err.extra.oauth2_response) 739 .. sep .. http.formencode(err.extra.oauth2_response)
565 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() }); 740 .. "&" .. http.formencode({ state = q.state, iss = get_issuer() });
566 module:log("warn", "Sending error response to client via redirect to %s", redirect_uri); 741 module:log("warn", "Sending error response to client via redirect to %s", redirect_uri);
567 return { 742 return {
568 status_code = 303; 743 status_code = 303;
569 headers = { 744 headers = {
745 cache_control = "no-store";
746 pragma = "no-cache";
570 location = redirect_uri; 747 location = redirect_uri;
571 }; 748 };
572 }; 749 };
573 end 750 end
574 751
575 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {"authorization_code", "password", "refresh_token"}) 752 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {
753 "authorization_code";
754 "password"; -- TODO Disable. The resource owner password credentials grant [RFC6749] MUST NOT be used.
755 "refresh_token";
756 device_uri;
757 })
758 if allowed_grant_type_handlers:contains("device_code") then
759 -- expand short form because that URI is long
760 module:log("debug", "Expanding %q to %q in '%s'", "device_code", device_uri, "allowed_oauth2_grant_types");
761 allowed_grant_type_handlers:remove("device_code");
762 allowed_grant_type_handlers:add(device_uri);
763 end
576 for handler_type in pairs(grant_type_handlers) do 764 for handler_type in pairs(grant_type_handlers) do
577 if not allowed_grant_type_handlers:contains(handler_type) then 765 if not allowed_grant_type_handlers:contains(handler_type) then
578 module:log("debug", "Grant type %q disabled", handler_type); 766 module:log("debug", "Grant type %q disabled", handler_type);
579 grant_type_handlers[handler_type] = nil; 767 grant_type_handlers[handler_type] = nil;
580 else 768 else
605 793
606 function handle_token_grant(event) 794 function handle_token_grant(event)
607 local credentials = get_request_credentials(event.request); 795 local credentials = get_request_credentials(event.request);
608 796
609 event.response.headers.content_type = "application/json"; 797 event.response.headers.content_type = "application/json";
610 local params = http.formdecode(event.request.body); 798 event.response.headers.cache_control = "no-store";
799 event.response.headers.pragma = "no-cache";
800 local params = strict_formdecode(event.request.body);
611 if not params then 801 if not params then
612 return error_response(event.request, oauth_error("invalid_request")); 802 return oauth_error("invalid_request", "Could not parse request body as 'application/x-www-form-urlencoded'");
613 end 803 end
614 804
615 if credentials and credentials.type == "basic" then 805 if credentials and credentials.type == "basic" then
616 -- client_secret_basic converted internally to client_secret_post 806 -- client_secret_basic converted internally to client_secret_post
617 params.client_id = http.urldecode(credentials.username); 807 params.client_id = http.urldecode(credentials.username);
619 end 809 end
620 810
621 local grant_type = params.grant_type 811 local grant_type = params.grant_type
622 local grant_handler = grant_type_handlers[grant_type]; 812 local grant_handler = grant_type_handlers[grant_type];
623 if not grant_handler then 813 if not grant_handler then
624 return error_response(event.request, oauth_error("unsupported_grant_type")); 814 return oauth_error("invalid_request", "No such grant type.");
625 end 815 end
626 return grant_handler(params); 816 return grant_handler(params);
627 end 817 end
628 818
629 local function handle_authorization_request(event) 819 local function handle_authorization_request(event)
630 local request = event.request; 820 local request = event.request;
631 821
822 -- Directly returning errors to the user before we have a validated client object
632 if not request.url.query then 823 if not request.url.query then
633 return error_response(request, oauth_error("invalid_request")); 824 return render_error(oauth_error("invalid_request", "Missing query parameters"));
634 end 825 end
635 local params = http.formdecode(request.url.query); 826 local params = strict_formdecode(request.url.query);
636 if not params then 827 if not params then
637 return error_response(request, oauth_error("invalid_request")); 828 return render_error(oauth_error("invalid_request", "Invalid query parameters"));
638 end 829 end
639 830
640 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end 831 if not params.client_id then
641 832 return render_error(oauth_error("invalid_request", "Missing 'client_id' parameter"));
642 local ok, client = jwt_verify(params.client_id); 833 end
643 834
644 if not ok then 835 local client = check_client(params.client_id);
645 return oauth_error("invalid_client", "incorrect credentials"); 836
646 end 837 if not client then
838 return render_error(oauth_error("invalid_request", "Invalid 'client_id' parameter"));
839 end
840
841 local redirect_uri = get_redirect_uri(client, params.redirect_uri);
842 if not redirect_uri then
843 return render_error(oauth_error("invalid_request", "Invalid 'redirect_uri' parameter"));
844 end
845 -- From this point we know that redirect_uri is safe to use
647 846
648 local client_response_types = set.new(array(client.response_types or { "code" })); 847 local client_response_types = set.new(array(client.response_types or { "code" }));
649 client_response_types = set.intersection(client_response_types, allowed_response_type_handlers); 848 client_response_types = set.intersection(client_response_types, allowed_response_type_handlers);
650 if not client_response_types:contains(params.response_type) then 849 if not client_response_types:contains(params.response_type) then
651 return oauth_error("invalid_client", "response_type not allowed"); 850 return error_response(request, redirect_uri, oauth_error("invalid_client", "'response_type' not allowed"));
851 end
852
853 local requested_scopes = parse_scopes(params.scope or "");
854 if client.scope then
855 local client_scopes = set.new(parse_scopes(client.scope));
856 requested_scopes:filter(function(scope)
857 return client_scopes:contains(scope);
858 end);
859 end
860
861 -- The 'prompt' parameter from OpenID Core
862 local prompt = set.new(parse_scopes(params.prompt or "select_account login consent"));
863 if prompt:contains("none") then
864 -- Client wants no interaction, only confirmation of prior login and
865 -- consent, but this is not implemented.
866 return error_response(request, redirect_uri, oauth_error("interaction_required"));
867 elseif not prompt:contains("select_account") and not params.login_hint then
868 -- TODO If the login page is split into account selection followed by login
869 -- (e.g. password), and then the account selection could be skipped iff the
870 -- 'login_hint' parameter is present.
871 return error_response(request, redirect_uri, oauth_error("account_selection_required"));
872 elseif not prompt:contains("login") then
873 -- Currently no cookies or such are used, so login is required every time.
874 return error_response(request, redirect_uri, oauth_error("login_required"));
875 elseif not prompt:contains("consent") then
876 -- Are there any circumstances when consent would be implied or assumed?
877 return error_response(request, redirect_uri, oauth_error("consent_required"));
652 end 878 end
653 879
654 local auth_state = get_auth_state(request); 880 local auth_state = get_auth_state(request);
655 if not auth_state.user then 881 if not auth_state.user then
656 -- Render login page 882 -- Render login page
657 return render_page(templates.login, { state = auth_state, client = client }); 883 local extra = {};
884 if params.login_hint then
885 extra.username_hint = (jid.prepped_split(params.login_hint));
886 end
887 return render_page(templates.login, { state = auth_state; client = client; extra = extra });
658 elseif auth_state.consent == nil then 888 elseif auth_state.consent == nil then
659 -- Render consent page 889 -- Render consent page
660 local scopes, requested_roles = split_scopes(parse_scopes(params.scope or "")); 890 local scopes, roles = split_scopes(requested_scopes);
661 local default_role = select_role(auth_state.user.username, requested_roles); 891 roles = user_assumable_roles(auth_state.user.username, roles);
662 local roles = array(it.values(usermanager.get_all_roles(module.host))):filter(function(role) 892 return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes+roles }, true);
663 return can_assume_role(auth_state.user.username, role.name);
664 end):sort(function(a, b)
665 return (a.priority or 0) < (b.priority or 0)
666 end):map(function(role)
667 return { name = role.name; selected = role.name == default_role };
668 end);
669 if not roles[2] then
670 -- Only one role to choose from, might as well skip the selector
671 roles = nil;
672 end
673 return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes; roles = roles }, true);
674 elseif not auth_state.consent then 893 elseif not auth_state.consent then
675 -- Notify client of rejection 894 -- Notify client of rejection
676 return error_response(request, oauth_error("access_denied")); 895 if redirect_uri == device_uri then
896 local is_device, device_state = verify_device_token(params.state);
897 if is_device then
898 local device_code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code));
899 local code = codes:get("device_code:" .. params.client_id .. "#" .. device_code);
900 code.error = oauth_error("access_denied");
901 code.expires = os.time() + 60;
902 codes:set("device_code:" .. params.client_id .. "#" .. device_code, code);
903 end
904 end
905 return error_response(request, redirect_uri, oauth_error("access_denied"));
677 end 906 end
678 -- else auth_state.consent == true 907 -- else auth_state.consent == true
679 908
680 params.scope = auth_state.scope; 909 local granted_scopes = auth_state.scopes
910 if client.scope then
911 local client_scopes = set.new(parse_scopes(client.scope));
912 granted_scopes:filter(function(scope)
913 return client_scopes:contains(scope);
914 end);
915 end
916
917 params.scope = granted_scopes:concat(" ");
681 918
682 local user_jid = jid.join(auth_state.user.username, module.host); 919 local user_jid = jid.join(auth_state.user.username, module.host);
683 local client_secret = make_client_secret(params.client_id); 920 local client_secret = make_client_secret(params.client_id);
684 local id_token_signer = jwt.new_signer("HS256", client_secret); 921 local id_token_signer = jwt.new_signer("HS256", client_secret);
685 local id_token = id_token_signer({ 922 local id_token = id_token_signer({
686 iss = get_issuer(); 923 iss = get_issuer();
687 sub = url.build({ scheme = "xmpp"; path = user_jid }); 924 sub = url.build({ scheme = "xmpp"; path = user_jid });
688 aud = params.client_id; 925 aud = params.client_id;
926 auth_time = auth_state.user.auth_time;
689 nonce = params.nonce; 927 nonce = params.nonce;
690 }); 928 });
691 local response_type = params.response_type; 929 local response_type = params.response_type;
692 local response_handler = response_type_handlers[response_type]; 930 local response_handler = response_type_handlers[response_type];
693 if not response_handler then 931 if not response_handler then
694 return error_response(request, oauth_error("unsupported_response_type")); 932 return error_response(request, redirect_uri, oauth_error("unsupported_response_type"));
695 end 933 end
696 return response_handler(client, params, user_jid, id_token); 934 local ret = response_handler(client, params, user_jid, id_token);
697 end 935 if errors.is_err(ret) then
936 return error_response(request, redirect_uri, ret);
937 end
938 return ret;
939 end
940
941 local function handle_device_authorization_request(event)
942 local request = event.request;
943
944 local credentials = get_request_credentials(request);
945
946 local params = strict_formdecode(request.body);
947 if not params then
948 return render_error(oauth_error("invalid_request", "Invalid query parameters"));
949 end
950
951 if credentials and credentials.type == "basic" then
952 -- client_secret_basic converted internally to client_secret_post
953 params.client_id = http.urldecode(credentials.username);
954 local client_secret = http.urldecode(credentials.password);
955
956 if not verify_client_secret(params.client_id, client_secret) then
957 module:log("debug", "client_secret mismatch");
958 return oauth_error("invalid_client", "incorrect credentials");
959 end
960 else
961 return 401;
962 end
963
964 local client = check_client(params.client_id);
965
966 if not client then
967 return render_error(oauth_error("invalid_request", "Invalid 'client_id' parameter"));
968 end
969
970 if not set.new(client.grant_types):contains(device_uri) then
971 return render_error(oauth_error("invalid_client", "Client not registered for device authorization grant"));
972 end
973
974 local requested_scopes = parse_scopes(params.scope or "");
975 if client.scope then
976 local client_scopes = set.new(parse_scopes(client.scope));
977 requested_scopes:filter(function(scope)
978 return client_scopes:contains(scope);
979 end);
980 end
981
982 -- TODO better code generator, this one should be easy to type from a
983 -- screen onto a phone
984 local user_code = (id.tiny() .. "-" .. id.tiny()):upper();
985 local collisions = 0;
986 while codes:get("authorization_code:" .. device_uri .. "#" .. user_code) do
987 collisions = collisions + 1;
988 if collisions > 10 then
989 return oauth_error("temporarily_unavailable");
990 end
991 user_code = (id.tiny() .. "-" .. id.tiny()):upper();
992 end
993 -- device code should be derivable after consent but not guessable by the user
994 local device_code = b64url(hashes.hmac_sha256(verification_key, user_code));
995 local verification_uri = module:http_url() .. "/device";
996 local verification_uri_complete = verification_uri .. "?" .. http.formencode({ user_code = user_code });
997
998 local expires = os.time() + 600;
999 local dc_ok = codes:set("device_code:" .. params.client_id .. "#" .. device_code, { expires = expires });
1000 local uc_ok = codes:set("user_code:" .. user_code,
1001 { user_code = user_code; expires = expires; client_id = params.client_id;
1002 scope = requested_scopes:concat(" ") });
1003 if not dc_ok or not uc_ok then
1004 return oauth_error("temporarily_unavailable");
1005 end
1006
1007 return {
1008 headers = { content_type = "application/json"; cache_control = "no-store"; pragma = "no-cache" };
1009 body = json.encode {
1010 device_code = device_code;
1011 user_code = user_code;
1012 verification_uri = verification_uri;
1013 verification_uri_complete = verification_uri_complete;
1014 expires_in = 600;
1015 interval = 5;
1016 };
1017 }
1018 end
1019
1020 local function handle_device_verification_request(event)
1021 local request = event.request;
1022 local params = strict_formdecode(request.url.query);
1023 if not params or not params.user_code then
1024 return render_page(templates.device, { client = false });
1025 end
1026
1027 local device_info = codes:get("user_code:" .. params.user_code);
1028 if not device_info or code_expired(device_info) or not codes:set("user_code:" .. params.user_code, nil) then
1029 return render_page(templates.device, {
1030 client = false;
1031 error = oauth_error("expired_token", "Incorrect or expired code");
1032 });
1033 end
1034
1035 return {
1036 status_code = 303;
1037 headers = {
1038 location = module:http_url() .. "/authorize" .. "?" .. http.formencode({
1039 client_id = device_info.client_id;
1040 redirect_uri = device_uri;
1041 response_type = "code";
1042 scope = device_info.scope;
1043 state = new_device_token({ user_code = params.user_code });
1044 });
1045 };
1046 }
1047 end
1048
1049 local strict_auth_revoke = module:get_option_boolean("oauth2_require_auth_revoke", false);
698 1050
699 local function handle_revocation_request(event) 1051 local function handle_revocation_request(event)
700 local request, response = event.request, event.response; 1052 local request, response = event.request, event.response;
1053 response.headers.cache_control = "no-store";
1054 response.headers.pragma = "no-cache";
701 if request.headers.authorization then 1055 if request.headers.authorization then
702 local credentials = get_request_credentials(request); 1056 local credentials = get_request_credentials(request);
703 if not credentials or credentials.type ~= "basic" then 1057 if not credentials or credentials.type ~= "basic" then
704 response.headers.www_authenticate = string.format("Basic realm=%q", module.host.."/"..module.name); 1058 response.headers.www_authenticate = string.format("Basic realm=%q", module.host.."/"..module.name);
705 return 401; 1059 return 401;
706 end 1060 end
707 -- OAuth "client" credentials 1061 -- OAuth "client" credentials
708 if not verify_client_secret(credentials.username, credentials.password) then 1062 if not verify_client_secret(credentials.username, credentials.password) then
709 return 401; 1063 return 401;
710 end 1064 end
711 end 1065 -- TODO check that it's their token I guess?
712 1066 elseif strict_auth_revoke then
713 local form_data = http.formdecode(event.request.body or ""); 1067 -- Why require auth to revoke a leaked token?
1068 response.headers.www_authenticate = string.format("Basic realm=%q", module.host.."/"..module.name);
1069 return 401;
1070 end
1071
1072 local form_data = strict_formdecode(event.request.body);
714 if not form_data or not form_data.token then 1073 if not form_data or not form_data.token then
715 response.headers.accept = "application/x-www-form-urlencoded"; 1074 response.headers.accept = "application/x-www-form-urlencoded";
716 return 415; 1075 return 415;
717 end 1076 end
718 local ok, err = tokens.revoke_token(form_data.token); 1077 local ok, err = tokens.revoke_token(form_data.token);
722 end 1081 end
723 return 200; 1082 return 200;
724 end 1083 end
725 1084
726 local registration_schema = { 1085 local registration_schema = {
1086 title = "OAuth 2.0 Dynamic Client Registration Protocol";
727 type = "object"; 1087 type = "object";
728 required = { 1088 required = {
729 -- These are shown to users in the template 1089 -- These are shown to users in the template
730 "client_name"; 1090 "client_name";
731 "client_uri"; 1091 "client_uri";
732 -- We need at least one redirect URI for things to work 1092 -- We need at least one redirect URI for things to work
733 "redirect_uris"; 1093 "redirect_uris";
734 }; 1094 };
735 properties = { 1095 properties = {
736 redirect_uris = { type = "array"; minLength = 1; items = { type = "string"; format = "uri" } }; 1096 redirect_uris = {
1097 title = "List of Redirect URIs";
1098 type = "array";
1099 minItems = 1;
1100 uniqueItems = true;
1101 items = { title = "Redirect URI"; type = "string"; format = "uri" };
1102 };
737 token_endpoint_auth_method = { 1103 token_endpoint_auth_method = {
1104 title = "Token Endpoint Authentication Method";
738 type = "string"; 1105 type = "string";
739 enum = { "none"; "client_secret_post"; "client_secret_basic" }; 1106 enum = { "none"; "client_secret_post"; "client_secret_basic" };
740 default = "client_secret_basic"; 1107 default = "client_secret_basic";
741 }; 1108 };
742 grant_types = { 1109 grant_types = {
1110 title = "Grant Types";
743 type = "array"; 1111 type = "array";
1112 minItems = 1;
1113 uniqueItems = true;
744 items = { 1114 items = {
745 type = "string"; 1115 type = "string";
746 enum = { 1116 enum = {
747 "authorization_code"; 1117 "authorization_code";
748 "implicit"; 1118 "implicit";
749 "password"; 1119 "password";
750 "client_credentials"; 1120 "client_credentials";
751 "refresh_token"; 1121 "refresh_token";
752 "urn:ietf:params:oauth:grant-type:jwt-bearer"; 1122 "urn:ietf:params:oauth:grant-type:jwt-bearer";
753 "urn:ietf:params:oauth:grant-type:saml2-bearer"; 1123 "urn:ietf:params:oauth:grant-type:saml2-bearer";
1124 device_uri;
754 }; 1125 };
755 }; 1126 };
756 default = { "authorization_code" }; 1127 default = { "authorization_code" };
757 }; 1128 };
758 application_type = { type = "string"; enum = { "native"; "web" }; default = "web" }; 1129 application_type = {
759 response_types = { type = "array"; items = { type = "string"; enum = { "code"; "token" } }; default = { "code" } }; 1130 title = "Application Type";
760 client_name = { type = "string" }; 1131 description = "Determines which kinds of redirect URIs the client may register. \z
761 client_uri = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1132 The value 'web' limits the client to https:// URLs with the same hostname as in 'client_uri' \z
762 logo_uri = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1133 while the value 'native' allows either loopback http:// URLs or application specific URIs.";
763 scope = { type = "string" }; 1134 type = "string";
764 contacts = { type = "array"; items = { type = "string"; format = "email" } }; 1135 enum = { "native"; "web" };
765 tos_uri = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1136 default = "web";
766 policy_uri = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1137 };
767 jwks_uri = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1138 response_types = {
768 jwks = { type = "object"; description = "JSON Web Key Set, RFC 7517" }; 1139 title = "Response Types";
769 software_id = { type = "string"; format = "uuid" }; 1140 type = "array";
770 software_version = { type = "string" }; 1141 minItems = 1;
771 }; 1142 uniqueItems = true;
772 luaPatternProperties = { 1143 items = { type = "string"; enum = { "code"; "token" } };
773 -- Localized versions of descriptive properties and URIs 1144 default = { "code" };
774 ["^client_name#"] = { description = "Localized version of 'client_name'"; type = "string" }; 1145 };
775 ["^[a-z_]+_uri#"] = { type = "string"; format = "uri"; luaPattern = "^https:" }; 1146 client_name = {
1147 title = "Client Name";
1148 description = "Human-readable name of the client, presented to the user in the consent dialog.";
1149 type = "string";
1150 };
1151 client_uri = {
1152 title = "Client URL";
1153 description = "Should be an link to a page with information about the client.";
1154 type = "string";
1155 format = "uri";
1156 pattern = "^https:";
1157 };
1158 logo_uri = {
1159 title = "Logo URL";
1160 description = "URL to the clients logotype (not currently used).";
1161 type = "string";
1162 format = "uri";
1163 pattern = "^https:";
1164 };
1165 scope = {
1166 title = "Scopes";
1167 description = "Space-separated list of scopes the client promises to restrict itself to.";
1168 type = "string";
1169 };
1170 contacts = {
1171 title = "Contact Addresses";
1172 description = "Addresses, typically email or URLs where the client developers can be contacted.";
1173 type = "array";
1174 minItems = 1;
1175 items = { type = "string"; format = "email" };
1176 };
1177 tos_uri = {
1178 title = "Terms of Service URL";
1179 description = "Link to Terms of Service for the client, presented to the user in the consent dialog. \z
1180 MUST be a https:// URL with hostname matching that of 'client_uri'.";
1181 type = "string";
1182 format = "uri";
1183 pattern = "^https:";
1184 };
1185 policy_uri = {
1186 title = "Privacy Policy URL";
1187 description = "Link to a Privacy Policy for the client. MUST be a https:// URL with hostname matching that of 'client_uri'.";
1188 type = "string";
1189 format = "uri";
1190 pattern = "^https:";
1191 };
1192 software_id = {
1193 title = "Software ID";
1194 description = "Unique identifier for the client software, common for all instances. Typically an UUID.";
1195 type = "string";
1196 format = "uuid";
1197 };
1198 software_version = {
1199 title = "Software Version";
1200 description = "Version of the client software being registered. \z
1201 E.g. to allow revoking all related tokens in the event of a security incident.";
1202 type = "string";
1203 example = "2.3.1";
1204 };
776 }; 1205 };
777 } 1206 }
778 1207
1208 -- Limit per-locale fields to allowed locales, partly to keep size of client_id
1209 -- down, partly because we don't yet use them for anything.
1210 -- Only relevant for user-visible strings and URIs.
1211 if allowed_locales[1] then
1212 local props = registration_schema.properties;
1213 for _, locale in ipairs(allowed_locales) do
1214 props["client_name#" .. locale] = props["client_name"];
1215 props["client_uri#" .. locale] = props["client_uri"];
1216 props["logo_uri#" .. locale] = props["logo_uri"];
1217 props["tos_uri#" .. locale] = props["tos_uri"];
1218 props["policy_uri#" .. locale] = props["policy_uri"];
1219 end
1220 end
1221
779 local function redirect_uri_allowed(redirect_uri, client_uri, app_type) 1222 local function redirect_uri_allowed(redirect_uri, client_uri, app_type)
780 local uri = url.parse(redirect_uri); 1223 local uri = url.parse(redirect_uri);
1224 if not uri.scheme then
1225 return false; -- no relative URLs
1226 end
781 if app_type == "native" then 1227 if app_type == "native" then
782 return uri.scheme == "http" and loopbacks:contains(uri.host) or uri.scheme ~= "https"; 1228 return uri.scheme == "http" and loopbacks:contains(uri.host) or redirect_uri == oob_uri or uri.scheme:find(".", 1, true) ~= nil;
783 elseif app_type == "web" then 1229 elseif app_type == "web" then
784 return uri.scheme == "https" and uri.host == client_uri.host; 1230 return uri.scheme == "https" and uri.host == client_uri.host;
785 end 1231 end
786 end 1232 end
787 1233
788 function create_client(client_metadata) 1234 function create_client(client_metadata)
789 if not schema.validate(registration_schema, client_metadata) then 1235 if not schema.validate(registration_schema, client_metadata) then
790 return nil, oauth_error("invalid_request", "Failed schema validation."); 1236 return nil, oauth_error("invalid_request", "Failed schema validation.");
1237 end
1238
1239 local client_uri = url.parse(client_metadata.client_uri);
1240 if not client_uri or client_uri.scheme ~= "https" or loopbacks:contains(client_uri.host) then
1241 return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri");
1242 end
1243
1244 if not client_metadata.application_type and redirect_uri_allowed(client_metadata.redirect_uris[1], client_uri, "native") then
1245 client_metadata.application_type = "native";
1246 -- else defaults to "web"
791 end 1247 end
792 1248
793 -- Fill in default values 1249 -- Fill in default values
794 for propname, propspec in pairs(registration_schema.properties) do 1250 for propname, propspec in pairs(registration_schema.properties) do
795 if client_metadata[propname] == nil and type(propspec) == "table" and propspec.default ~= nil then 1251 if client_metadata[propname] == nil and type(propspec) == "table" and propspec.default ~= nil then
796 client_metadata[propname] = propspec.default; 1252 client_metadata[propname] = propspec.default;
797 end 1253 end
798 end 1254 end
799 1255
800 local client_uri = url.parse(client_metadata.client_uri); 1256 -- MUST ignore any metadata that it does not understand
801 if not client_uri or client_uri.scheme ~= "https" or loopbacks:contains(client_uri.host) then 1257 for propname in pairs(client_metadata) do
802 return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri"); 1258 if not registration_schema.properties[propname] then
1259 client_metadata[propname] = nil;
1260 end
803 end 1261 end
804 1262
805 for _, redirect_uri in ipairs(client_metadata.redirect_uris) do 1263 for _, redirect_uri in ipairs(client_metadata.redirect_uris) do
806 if not redirect_uri_allowed(redirect_uri, client_uri, client_metadata.application_type) then 1264 if not redirect_uri_allowed(redirect_uri, client_uri, client_metadata.application_type) then
807 return nil, oauth_error("invalid_redirect_uri", "Invalid, insecure or inappropriate redirect URI."); 1265 return nil, oauth_error("invalid_redirect_uri", "Invalid, insecure or inappropriate redirect URI.");
814 return nil, oauth_error("invalid_client_metadata", "Invalid, insecure or inappropriate informative URI"); 1272 return nil, oauth_error("invalid_client_metadata", "Invalid, insecure or inappropriate informative URI");
815 end 1273 end
816 end 1274 end
817 end 1275 end
818 1276
819 for k, v in pairs(client_metadata) do
820 local base_k = k:match"^([^#]+)#" or k;
821 if not registration_schema.properties[base_k] or k:find"^client_uri#" then
822 -- Ignore and strip unknown extra properties
823 client_metadata[k] = nil;
824 elseif k:find"_uri#" then
825 -- Localized URIs should be secure too
826 if not redirect_uri_allowed(v, client_uri, "web") then
827 return nil, oauth_error("invalid_client_metadata", "Invalid, insecure or inappropriate informative URI");
828 end
829 end
830 end
831
832 local grant_types = set.new(client_metadata.grant_types); 1277 local grant_types = set.new(client_metadata.grant_types);
833 local response_types = set.new(client_metadata.response_types); 1278 local response_types = set.new(client_metadata.response_types);
834 1279
835 if grant_types:contains("authorization_code") and not response_types:contains("code") then 1280 if grant_types:contains("authorization_code") and not response_types:contains("code") then
836 return nil, oauth_error("invalid_client_metadata", "Inconsistency between 'grant_types' and 'response_types'"); 1281 return nil, oauth_error("invalid_client_metadata", "Inconsistency between 'grant_types' and 'response_types'");
842 return nil, oauth_error("invalid_client_metadata", "No allowed 'grant_types' specified"); 1287 return nil, oauth_error("invalid_client_metadata", "No allowed 'grant_types' specified");
843 elseif set.intersection(response_types, allowed_response_type_handlers):empty() then 1288 elseif set.intersection(response_types, allowed_response_type_handlers):empty() then
844 return nil, oauth_error("invalid_client_metadata", "No allowed 'response_types' specified"); 1289 return nil, oauth_error("invalid_client_metadata", "No allowed 'response_types' specified");
845 end 1290 end
846 1291
847 -- Ensure each signed client_id JWT is unique, short ID and issued at
848 -- timestamp should be sufficient to rule out brute force attacks
849 client_metadata.nonce = id.short();
850
851 -- Do we want to keep everything? 1292 -- Do we want to keep everything?
852 local client_id = jwt_sign(client_metadata); 1293 local client_id = sign_client(client_metadata);
853 1294
854 client_metadata.client_id = client_id; 1295 client_metadata.client_id = client_id;
855 client_metadata.client_id_issued_at = os.time(); 1296 client_metadata.client_id_issued_at = os.time();
856 1297
857 if client_metadata.token_endpoint_auth_method ~= "none" then 1298 if client_metadata.token_endpoint_auth_method ~= "none" then
858 local client_secret = make_client_secret(client_id); 1299 -- Ensure that each client_id JWT with a client_secret is unique.
1300 -- A short ID along with the issued at timestamp should be sufficient to
1301 -- rule out brute force attacks.
1302 -- Not needed for public clients without a secret, but those are expected
1303 -- to be uncommon since they can only do the insecure implicit flow.
1304 client_metadata.nonce = id.short();
1305
1306 local client_secret = make_client_secret(client_id, client_metadata);
859 client_metadata.client_secret = client_secret; 1307 client_metadata.client_secret = client_secret;
860 client_metadata.client_secret_expires_at = 0; 1308 client_metadata.client_secret_expires_at = 0;
861 1309
862 if not registration_options.accept_expired then 1310 if not registration_options.accept_expired then
863 client_metadata.client_secret_expires_at = client_metadata.client_id_issued_at + (registration_options.default_ttl or 3600); 1311 client_metadata.client_secret_expires_at = client_metadata.client_id_issued_at + (registration_options.default_ttl or 3600);
877 local response, err = create_client(client_metadata); 1325 local response, err = create_client(client_metadata);
878 if err then return err end 1326 if err then return err end
879 1327
880 return { 1328 return {
881 status_code = 201; 1329 status_code = 201;
882 headers = { content_type = "application/json" }; 1330 headers = {
1331 cache_control = "no-store";
1332 pragma = "no-cache";
1333 content_type = "application/json";
1334 };
883 body = json.encode(response); 1335 body = json.encode(response);
884 }; 1336 };
885 end 1337 end
886 1338
887 if not registration_key then 1339 if not registration_key then
888 module:log("info", "No 'oauth2_registration_key', dynamic client registration disabled") 1340 module:log("info", "No 'oauth2_registration_key', dynamic client registration disabled")
889 handle_authorization_request = nil 1341 handle_authorization_request = nil
890 handle_register_request = nil 1342 handle_register_request = nil
1343 handle_device_authorization_request = nil
1344 handle_device_verification_request = nil
891 end 1345 end
892 1346
893 local function handle_userinfo_request(event) 1347 local function handle_userinfo_request(event)
894 local request = event.request; 1348 local request = event.request;
895 local credentials = get_request_credentials(request); 1349 local credentials = get_request_credentials(request);
939 }; 1393 };
940 end 1394 end
941 1395
942 module:depends("http"); 1396 module:depends("http");
943 module:provides("http", { 1397 module:provides("http", {
1398 cors = { enabled = true; credentials = true };
944 route = { 1399 route = {
945 -- OAuth 2.0 in 5 simple steps! 1400 -- OAuth 2.0 in 5 simple steps!
946 -- This is the normal 'authorization_code' flow. 1401 -- This is the normal 'authorization_code' flow.
947 1402
948 -- Step 1. Create OAuth client 1403 -- Step 1. Create OAuth client
949 ["POST /register"] = handle_register_request; 1404 ["POST /register"] = handle_register_request;
950 1405
1406 -- Device flow
1407 ["POST /device"] = handle_device_authorization_request;
1408 ["GET /device"] = handle_device_verification_request;
1409
951 -- Step 2. User-facing login and consent view 1410 -- Step 2. User-facing login and consent view
952 ["GET /authorize"] = handle_authorization_request; 1411 ["GET /authorize"] = handle_authorization_request;
953 ["POST /authorize"] = handle_authorization_request; 1412 ["POST /authorize"] = handle_authorization_request;
1413 ["OPTIONS /authorize"] = { status_code = 403; body = "" };
954 1414
955 -- Step 3. User is redirected to the 'redirect_uri' along with an 1415 -- Step 3. User is redirected to the 'redirect_uri' along with an
956 -- authorization code. In the insecure 'implicit' flow, the access token 1416 -- authorization code. In the insecure 'implicit' flow, the access token
957 -- is delivered here. 1417 -- is delivered here.
958 1418
970 -- Optional static content for templates 1430 -- Optional static content for templates
971 ["GET /style.css"] = templates.css and { 1431 ["GET /style.css"] = templates.css and {
972 headers = { 1432 headers = {
973 ["Content-Type"] = "text/css"; 1433 ["Content-Type"] = "text/css";
974 }; 1434 };
975 body = _render_html(templates.css, module:get_option("oauth2_template_style")); 1435 body = templates.css;
976 } or nil; 1436 } or nil;
977 ["GET /script.js"] = templates.js and { 1437 ["GET /script.js"] = templates.js and {
978 headers = { 1438 headers = {
979 ["Content-Type"] = "text/javascript"; 1439 ["Content-Type"] = "text/javascript";
980 }; 1440 };
1000 return json.encode(oauth2_response); 1460 return json.encode(oauth2_response);
1001 end, 5); 1461 end, 5);
1002 1462
1003 -- OIDC Discovery 1463 -- OIDC Discovery
1004 1464
1465 function get_authorization_server_metadata()
1466 if authorization_server_metadata then
1467 return authorization_server_metadata;
1468 end
1469 authorization_server_metadata = {
1470 -- RFC 8414: OAuth 2.0 Authorization Server Metadata
1471 issuer = get_issuer();
1472 authorization_endpoint = handle_authorization_request and module:http_url() .. "/authorize" or nil;
1473 token_endpoint = handle_token_grant and module:http_url() .. "/token" or nil;
1474 registration_endpoint = handle_register_request and module:http_url() .. "/register" or nil;
1475 scopes_supported = usermanager.get_all_roles
1476 and array(it.keys(usermanager.get_all_roles(module.host))):push("xmpp"):append(array(openid_claims:items()));
1477 response_types_supported = array(it.keys(response_type_handlers));
1478 token_endpoint_auth_methods_supported = array({ "client_secret_post"; "client_secret_basic" });
1479 op_policy_uri = module:get_option_string("oauth2_policy_url", nil);
1480 op_tos_uri = module:get_option_string("oauth2_terms_url", nil);
1481 revocation_endpoint = handle_revocation_request and module:http_url() .. "/revoke" or nil;
1482 revocation_endpoint_auth_methods_supported = array({ "client_secret_basic" });
1483 device_authorization_endpoint = handle_device_authorization_request and module:http_url() .. "/device";
1484 code_challenge_methods_supported = array(it.keys(verifier_transforms));
1485 grant_types_supported = array(it.keys(grant_type_handlers));
1486 response_modes_supported = array(it.keys(response_type_handlers)):map(tmap { token = "fragment"; code = "query" });
1487 authorization_response_iss_parameter_supported = true;
1488 service_documentation = module:get_option_string("oauth2_service_documentation", "https://modules.prosody.im/mod_http_oauth2.html");
1489 ui_locales_supported = allowed_locales[1] and allowed_locales;
1490
1491 -- OpenID
1492 userinfo_endpoint = handle_register_request and module:http_url() .. "/userinfo" or nil;
1493 jwks_uri = nil; -- REQUIRED in OpenID Discovery but not in OAuth 2.0 Metadata
1494 id_token_signing_alg_values_supported = { "HS256" }; -- The algorithm RS256 MUST be included, but we use HS256 and client_secret as shared key.
1495 }
1496 return authorization_server_metadata;
1497 end
1498
1005 module:provides("http", { 1499 module:provides("http", {
1006 name = "oauth2-discovery"; 1500 name = "oauth2-discovery";
1007 default_path = "/.well-known/oauth-authorization-server"; 1501 default_path = "/.well-known/oauth-authorization-server";
1502 cors = { enabled = true };
1008 route = { 1503 route = {
1009 ["GET"] = { 1504 ["GET"] = function()
1010 headers = { content_type = "application/json" }; 1505 return {
1011 body = json.encode { 1506 headers = { content_type = "application/json" };
1012 -- RFC 8414: OAuth 2.0 Authorization Server Metadata 1507 body = json.encode(get_authorization_server_metadata());
1013 issuer = get_issuer(); 1508 }
1014 authorization_endpoint = handle_authorization_request and module:http_url() .. "/authorize" or nil; 1509 end
1015 token_endpoint = handle_token_grant and module:http_url() .. "/token" or nil;
1016 jwks_uri = nil; -- TODO?
1017 registration_endpoint = handle_register_request and module:http_url() .. "/register" or nil;
1018 scopes_supported = usermanager.get_all_roles and array(it.keys(usermanager.get_all_roles(module.host))):append(array(openid_claims:items()));
1019 response_types_supported = array(it.keys(response_type_handlers));
1020 token_endpoint_auth_methods_supported = array({ "client_secret_post"; "client_secret_basic" });
1021 op_policy_uri = module:get_option_string("oauth2_policy_url", nil);
1022 op_tos_uri = module:get_option_string("oauth2_terms_url", nil);
1023 revocation_endpoint = handle_revocation_request and module:http_url() .. "/revoke" or nil;
1024 revocation_endpoint_auth_methods_supported = array({ "client_secret_basic" });
1025 code_challenge_methods_supported = array(it.keys(verifier_transforms));
1026 grant_types_supported = array(it.keys(response_type_handlers)):map(tmap { token = "implicit"; code = "authorization_code" });
1027 response_modes_supported = array(it.keys(response_type_handlers)):map(tmap { token = "fragment"; code = "query" });
1028 authorization_response_iss_parameter_supported = true;
1029 service_documentation = module:get_option_string("oauth2_service_documentation", "https://modules.prosody.im/mod_http_oauth2.html");
1030
1031 -- OpenID
1032 userinfo_endpoint = handle_register_request and module:http_url() .. "/userinfo" or nil;
1033 id_token_signing_alg_values_supported = { "HS256" };
1034 };
1035 };
1036 }; 1510 };
1037 }); 1511 });
1038 1512
1039 module:shared("tokenauth/oauthbearer_config").oidc_discovery_url = module:http_url("oauth2-discovery", "/.well-known/oauth-authorization-server"); 1513 module:shared("tokenauth/oauthbearer_config").oidc_discovery_url = module:http_url("oauth2-discovery", "/.well-known/oauth-authorization-server");