Password reset
This commit is contained in:
parent
664190cdae
commit
765299ac2f
5 changed files with 259 additions and 1 deletions
|
@ -5,3 +5,5 @@
|
|||
{["users", uid, "password"], 'elmdap-password', []}.
|
||||
{["users"], 'elmdap-users', []}.
|
||||
{["groups"], 'elmdap-groups', []}.
|
||||
{["reset"], 'elmdap-reset-generate', []}.
|
||||
{["reset", token], 'elmdap-reset', []}.
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
{deps, [
|
||||
{lfe, {git, "git://github.com/rvirding/lfe", {tag, "1.0"}}},
|
||||
{calrissian, {git, "git://github.com/correl/calrissian", {branch, "rebar3"}}},
|
||||
{webmachine, ".*", {git, "git://github.com/basho/webmachine.git", {branch, "master"}}}
|
||||
{webmachine, ".*", {git, "git://github.com/basho/webmachine.git", {branch, "master"}}},
|
||||
jwt,
|
||||
gen_smtp
|
||||
]}.
|
||||
|
||||
{plugins, [
|
||||
|
|
131
src/elmdap-reset-generate.lfe
Normal file
131
src/elmdap-reset-generate.lfe
Normal file
|
@ -0,0 +1,131 @@
|
|||
(defmodule elmdap-reset-generate
|
||||
(export (init 1)
|
||||
(service_available 2)
|
||||
(allowed_methods 2)
|
||||
(content_types_provided 2)
|
||||
(resource_exists 2)
|
||||
(malformed_request 2)
|
||||
(allow_missing_post 2)
|
||||
(process_post 2)
|
||||
(to_json 2)))
|
||||
|
||||
(include-lib "webmachine/include/webmachine.hrl")
|
||||
(include-lib "calrissian/include/monads.lfe")
|
||||
|
||||
(defrecord state
|
||||
email
|
||||
entry
|
||||
connection
|
||||
hostname
|
||||
port
|
||||
secret
|
||||
expires
|
||||
smtp)
|
||||
|
||||
(defrecord smtp
|
||||
relay
|
||||
from
|
||||
username
|
||||
password
|
||||
ssl)
|
||||
|
||||
(defun init (args)
|
||||
`#(ok ,(make-state hostname (os:getenv "ELMDAP_HOSTNAME" (smtp_util:guess_FQDN))
|
||||
port (os:getenv "WEBMACHINE_PORT" "8080")
|
||||
secret (os:getenv "ELMDAP_SECRET" "secret")
|
||||
expires (erlang:list_to_integer (os:getenv "ELMDAP_EXPIRES" "3600"))
|
||||
smtp (make-smtp
|
||||
from (os:getenv "SMTP_FROM_ADDRESS")
|
||||
relay (os:getenv "SMTP_RELAY")
|
||||
username (os:getenv "SMTP_USERNAME")
|
||||
password (os:getenv "SMTP_PASSWORD")
|
||||
ssl (== "TRUE" (string:to_upper (os:getenv "SMTP_SSL" "FALSE")))))))
|
||||
|
||||
(defun service_available (req-data state)
|
||||
(case (elmdap:open)
|
||||
((tuple 'ok connection)
|
||||
`#(true ,req-data ,(set-state state connection connection)))
|
||||
(error
|
||||
(error_logger:warning_report `[service-unavailable
|
||||
,error])
|
||||
`#(false ,req-data ,state))))
|
||||
|
||||
(defun allowed_methods (req-data state)
|
||||
`#([POST] ,req-data ,state))
|
||||
|
||||
(defun content_types_provided (req-data state)
|
||||
`#((#("application/json" to_json)) ,req-data ,state))
|
||||
|
||||
(defun resource_exists (req-data state)
|
||||
(case (elmdap:from-email (state-connection state) (state-email state))
|
||||
(`#(ok ,entry)
|
||||
`#(true ,req-data ,(set-state state entry entry)))
|
||||
(_ `#(false ,req-data ,state))))
|
||||
|
||||
(defun to_json (req-data state)
|
||||
(tuple
|
||||
(mochijson:encode (state-email state))
|
||||
req-data
|
||||
state))
|
||||
|
||||
(defun malformed_request (req-data state)
|
||||
(case (do-m (monad 'error)
|
||||
(data <- (try (case (mochijson:decode (wrq:req_body req-data))
|
||||
(`#(struct ,plist) `#(ok ,plist))
|
||||
(_ #(error malformed)))
|
||||
(catch (e `#(error ,e)))))
|
||||
(email <- (case (proplists:get_value "email" data)
|
||||
('undefined #(error missing-email))
|
||||
(email `#(ok ,email))))
|
||||
(return (monad 'error) (set-state state email email)))
|
||||
(`#(ok ,newstate) `#(false ,req-data ,newstate))
|
||||
(_ `#(true ,req-data ,state))))
|
||||
|
||||
(defun allow_missing_post (req-data state)
|
||||
`#(true ,req-data ,state))
|
||||
|
||||
(defun process_post (req-data state)
|
||||
(error_logger:info_report `[password-reset-requested
|
||||
#(state ,state)])
|
||||
(if (/= 'undefined (state-entry state))
|
||||
(send-email state))
|
||||
`#(true ,req-data ,state))
|
||||
|
||||
(defun send-email (state)
|
||||
(let* ((entry (state-entry state))
|
||||
(attrs (elmdap:entry-attributes entry))
|
||||
(email (state-email state))
|
||||
(name (proplists:get_value "displayName" attrs
|
||||
(proplists:get_value "cn" attrs)))
|
||||
(claims `[#(dn ,(erlang:list_to_binary (elmdap-entry:dn entry)))
|
||||
#(email ,(erlang:list_to_binary email))])
|
||||
(expires (state-expires state))
|
||||
(secret (erlang:list_to_binary (state-secret state)))
|
||||
(`#(ok ,token) (jwt:encode #"HS256" claims expires secret))
|
||||
(smtp (state-smtp state))
|
||||
(email `#(,(smtp-from smtp)
|
||||
[,email]
|
||||
,(erlang:iolist_to_binary
|
||||
`["Subject: LDAP Password Reset\r\n"
|
||||
"From: CoreDial LDAP <" ,(smtp-from smtp) ">\r\n"
|
||||
"\r\n"
|
||||
,name ",\r\n"
|
||||
"Please visit the following URL to reset the LDAP password:\r\n"
|
||||
"http://" ,(http-host (state-hostname state) (state-port state)) "/reset/" ,token])))
|
||||
(options `[#(relay ,(smtp-relay smtp))
|
||||
#(username ,(smtp-username smtp))
|
||||
#(password ,(smtp-password smtp))
|
||||
#(ssl ,(smtp-ssl smtp))]))
|
||||
(let ((result (gen_smtp_client:send
|
||||
email
|
||||
options)))
|
||||
(error_logger:info_report `[sending-email
|
||||
#(email ,email)
|
||||
#(options ,options)
|
||||
#(result ,result)])
|
||||
result)))
|
||||
|
||||
(defun http-host (hostname port)
|
||||
(case port
|
||||
("80" hostname)
|
||||
(_ (lists:flatten `[,hostname ":" ,port]))))
|
109
src/elmdap-reset.lfe
Normal file
109
src/elmdap-reset.lfe
Normal file
|
@ -0,0 +1,109 @@
|
|||
(defmodule elmdap-reset
|
||||
(export (init 1)
|
||||
(service_available 2)
|
||||
(is_authorized 2)
|
||||
(allowed_methods 2)
|
||||
(content_types_provided 2)
|
||||
(resource_exists 2)
|
||||
(malformed_request 2)
|
||||
(allow_missing_post 2)
|
||||
(process_post 2)
|
||||
(to_json 2)))
|
||||
|
||||
(include-lib "webmachine/include/webmachine.hrl")
|
||||
(include-lib "calrissian/include/monads.lfe")
|
||||
|
||||
(defrecord state
|
||||
connection
|
||||
dn
|
||||
email
|
||||
password)
|
||||
|
||||
(defun init (args)
|
||||
`#(ok ,(make-state)))
|
||||
|
||||
(defun service_available (req-data state)
|
||||
(case (do-m (monad 'error)
|
||||
(connection <- (elmdap:open))
|
||||
(eldap:simple_bind connection
|
||||
(os:getenv "LDAP_BIND_DN")
|
||||
(os:getenv "LDAP_BIND_PASSWORD"))
|
||||
(return (monad 'error) connection))
|
||||
((tuple 'ok connection)
|
||||
`#(true ,req-data ,(set-state state connection connection)))
|
||||
(error
|
||||
(error_logger:warning_report `[service-unavailable
|
||||
,error])
|
||||
`#(false ,req-data ,state))))
|
||||
|
||||
(defun is_authorized (req-data state)
|
||||
(error_logger:info_report 'is_authorized)
|
||||
(let ((secret (os:getenv "ELMDAP_SECRET" "secret"))
|
||||
(`[#(token ,token)] (wrq:path_info req-data)))
|
||||
(case (do-m (monad 'error)
|
||||
(claims <- (jwt:decode (erlang:list_to_binary token) (erlang:list_to_binary secret)))
|
||||
(dn <- (maps:find #"dn" claims))
|
||||
(email <- (maps:find #"email" claims))
|
||||
(return (monad 'error) (set-state state
|
||||
dn (erlang:binary_to_list dn)
|
||||
email (erlang:binary_to_list email))))
|
||||
(`#(ok ,newstate) `#(true ,req-data ,newstate))
|
||||
(`#(error ,e)
|
||||
`#("Bearer realm=Elmdap" ,req-data ,state)))))
|
||||
|
||||
(defun allowed_methods (req-data state)
|
||||
`#([POST] ,req-data ,state))
|
||||
|
||||
(defun content_types_provided (req-data state)
|
||||
`#((#("application/json" to_json)) ,req-data ,state))
|
||||
|
||||
(defun resource_exists (req-data state)
|
||||
(case (get-entry (state-connection state) (state-dn state) (state-email state))
|
||||
(`#(ok ,_) `#(true ,req-data ,state))
|
||||
(`#(error, _) `#(false ,req-data ,state))))
|
||||
|
||||
(defun to_json (req-data state)
|
||||
(tuple
|
||||
(mochijson:encode (state-dn state))
|
||||
req-data
|
||||
state))
|
||||
|
||||
(defun malformed_request (req-data state)
|
||||
(case (do-m (monad 'error)
|
||||
(data <- (try (case (mochijson:decode (wrq:req_body req-data))
|
||||
(`#(struct ,plist) `#(ok ,plist))
|
||||
(_ #(error malformed)))
|
||||
(catch (e `#(error ,e)))))
|
||||
(password <- (case (proplists:get_value "password" data)
|
||||
('undefined #(error missing-password))
|
||||
(email `#(ok ,email))))
|
||||
(return (monad 'error) (set-state state password password)))
|
||||
(`#(ok ,newstate) `#(false ,req-data ,newstate))
|
||||
(_ `#(true ,req-data ,state))))
|
||||
|
||||
(defun allow_missing_post (req-data state)
|
||||
`#(false ,req-data ,state))
|
||||
|
||||
(defun process_post (req-data state)
|
||||
(case (eldap:modify_password
|
||||
(state-connection state)
|
||||
(state-dn state)
|
||||
(state-password state))
|
||||
('ok `#(true ,req-data ,state))
|
||||
(e
|
||||
(error_logger:error_report `[resetting-password ,e])
|
||||
`#(false ,req-data ,state))))
|
||||
|
||||
(defun get-entry (connection dn email)
|
||||
(error_logger:info_report `[#(connection ,connection) #(dn ,dn) #(email ,email)])
|
||||
(let* ((base dn)
|
||||
(filter (eldap:equalityMatch "mail" email))
|
||||
(`#(ok #(eldap_search_result ,results ,_))
|
||||
(eldap:search connection
|
||||
`[#(base ,base)
|
||||
#(filter ,filter)
|
||||
#(attributes ["cn" "userPassword"])]))
|
||||
((cons entry _) results))
|
||||
(case results
|
||||
(`[,entry] `#(ok ,entry))
|
||||
(_ #(error not_found)))))
|
|
@ -32,6 +32,20 @@
|
|||
((cons (= entry `#(eldap_entry ,dn ,attributes)) _) `#(ok ,entry))
|
||||
(_ #(error not_found)))))
|
||||
|
||||
(defun from-email (connection email)
|
||||
(let* ((`#(ok ,connection) (open))
|
||||
(base "ou=people,dc=coredial,dc=com")
|
||||
(filter (eldap:equalityMatch "mail" email))
|
||||
(`#(ok #(eldap_search_result ,results ,_))
|
||||
(eldap:search connection
|
||||
`[#(base ,base)
|
||||
#(filter ,filter)]))
|
||||
)
|
||||
(eldap:close connection)
|
||||
(case results
|
||||
((cons (= entry `#(eldap_entry ,dn ,attributes)) _) `#(ok ,entry))
|
||||
(_ #(error not_found)))))
|
||||
|
||||
(defun open ()
|
||||
(let ((hosts `[,(os:getenv "LDAP_HOST" "localhost")])
|
||||
(options `[#(port ,(list_to_integer (os:getenv "LDAP_PORT" "389")))]))
|
||||
|
|
Loading…
Reference in a new issue