site/api/oauth.lua (120 lines of code) (raw):

--[[ Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ]]-- -- This is oauth.lua - an oauth providing script for ponymail local JSON = require 'cjson' local http = require 'socket.http' local elastic = require 'lib/elastic' local https = require 'ssl.https' local user = require 'lib/user' local cross = require 'lib/cross' local config = require 'lib/config' function handle(r) cross.contentType(r, "application/json") local get = r:parseargs() local post = r:parsebody() local valid, json local scheme = "https" if r.port == 80 then scheme = "http" end local oauth_domain = "" if config.oauth_fields and config.oauth_fields[get.key] then for k, v in pairs(config.oauth_fields[get.key]) do r.args = r.args .. ("&%s=%s"):format(k,v) end if config.oauth_fields[get.key].oauth_token then get.oauth_token = config.oauth_fields[get.key].oauth_token end end -- Google Auth callback if get.oauth_token and get.oauth_token:match("^https://www.google") and get.code then oauth_domain = "www.googleapis.com" local result = https.request("https://www.googleapis.com/oauth2/v4/token", ("client_secret=%s&code=%s&client_id=%s&grant_type=authorization_code&redirect_uri=%s" ):format( r:escape(config.oauth_fields.google.client_secret), r:escape(get.code), r:escape(config.oauth_fields.google.client_id), r:escape(config.oauth_fields.google.redirect_uri) )) valid, json = pcall(function() return JSON.decode(result) end) if valid and json and json.access_token then local ac = json.access_token local result = https.request("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" .. r:escape(ac)) valid, json = pcall(function() return JSON.decode(result) end) else json = nil valid = false end -- GitHub Auth callback elseif get.oauth_token and get.key == 'github' then local result = https.request(get.oauth_token, r.args) local token = result:match("(access_token=[a-f0-9]+)") if token then local result = https.request("https://api.github.com/user/emails?" .. token) valid, json = pcall(function() return JSON.decode(result) end) if valid and json then json = json[1] end end -- OAuth.online callback elseif get.oauth_token and get.key == 'online' then local result = https.request("https://verify.oauth.online/token", r.args) valid, json = pcall(function() return JSON.decode(result) end) -- Generic callback (like ASF Oauth2) elseif get.state and get.code and get.oauth_token then oauth_domain = get.oauth_token:match("https?://(.-)/") local result = https.request(get.oauth_token, r.args) valid, json = pcall(function() return JSON.decode(result) end) --[[ CAS or other internal auth mechanism though request or env headers: Remember to set the 'internal' field vars in config.lua to enable this, for instance: ... oauth_fields = { internal = { email = 'CAS-EMAIL', name = 'CAS-NAME', uid = 'REMOTE-USER', env = 'subprocess' -- use environment vars instead of request headers } }, oauth_admin = { "localhost" }, ... ]]-- elseif get.key == 'internal' and config.oauth_fields['internal'] then oauth_domain = "localhost" local tbl = r.headers_in if config.oauth_fields['internal'].env and config.oauth_fields['internal'].env == 'subprocess' then tbl = r.subprocess_env end json = { email = tbl[config.oauth_fields['internal']['email'] or 0], name = tbl[config.oauth_fields['internal']['name'] or 0], uid = tbl[config.oauth_fields['internal']['uid'] or 0] } -- if httpd borks, bail! if json.uid == '(null)' or json.email == '(null)' then json = nil end -- Only use internal thing if localhost is trusted for k, v in pairs(config.admin_oauth or {}) do if r.strcmp_match(oauth_domain, v) then valid = true break end end end -- Did we get something useful from the backend? if valid and json then local eml = json.email local fname = json.fullname or json.name or json.email local admin = json.isMember -- If we got an email and a name, log in the user and set cookie etc if eml and fname then local cid = json.uid or json.email -- Does the user exist already? local oaccount = user.get(r, cid) local usr = {} if oaccount then usr.preferences = oaccount.preferences else usr.preferences = {} end usr.gauth = get.id_token usr.fullname = fname -- if the oauth provider can set admin status, do so if needed local authority = false for k, v in pairs(config.admin_oauth or {}) do if r.strcmp_match(oauth_domain, v) then authority = true break end end if authority then usr.admin = admin end usr.email = eml usr.uid = json.uid usr.oauth_used = oauth_domain user.update(r, cid, usr) r:puts[[{"okay": true, "msg": "Logged in successfully!"}]] -- didn't get email or name, bork! else r:puts[[{"okay": false, "msg": "Erroneous or missing response from backend!"}]] end -- Backend borked, let the user know else r:puts[[{"okay": false, "msg": "Invalid oauth response!"}]] end return cross.OK end cross.start(handle)