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", uid, "password"], 'elmdap-password', []}.
|
||||||
{["users"], 'elmdap-users', []}.
|
{["users"], 'elmdap-users', []}.
|
||||||
{["groups"], 'elmdap-groups', []}.
|
{["groups"], 'elmdap-groups', []}.
|
||||||
|
{["reset"], 'elmdap-reset-generate', []}.
|
||||||
|
{["reset", token], 'elmdap-reset', []}.
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
{deps, [
|
{deps, [
|
||||||
{lfe, {git, "git://github.com/rvirding/lfe", {tag, "1.0"}}},
|
{lfe, {git, "git://github.com/rvirding/lfe", {tag, "1.0"}}},
|
||||||
{calrissian, {git, "git://github.com/correl/calrissian", {branch, "rebar3"}}},
|
{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, [
|
{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))
|
((cons (= entry `#(eldap_entry ,dn ,attributes)) _) `#(ok ,entry))
|
||||||
(_ #(error not_found)))))
|
(_ #(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 ()
|
(defun open ()
|
||||||
(let ((hosts `[,(os:getenv "LDAP_HOST" "localhost")])
|
(let ((hosts `[,(os:getenv "LDAP_HOST" "localhost")])
|
||||||
(options `[#(port ,(list_to_integer (os:getenv "LDAP_PORT" "389")))]))
|
(options `[#(port ,(list_to_integer (os:getenv "LDAP_PORT" "389")))]))
|
||||||
|
|
Loading…
Reference in a new issue