Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
7.1KB

  1. #|
  2. src/web/user.lisp
  3. User Interaction Frontend
  4. |#
  5. (in-package :cl-deck-builder2.web)
  6. ;; This seems to be okay. The intent of overriding HERMETIC::AUTHORIZE
  7. ;; is we use MITO-AUTH for user authentication and their class isn't
  8. ;; just a password hash. So this is an undocumented way of customizing
  9. ;; authorization method using HERMETIC.
  10. (defun hermetic::authorize (username password)
  11. (let ((object (funcall hermetic::*user-p* username)))
  12. (and object
  13. (string= (mito-auth:password-hash object)
  14. (mito-auth::make-password-hash
  15. password
  16. (mito-auth:password-salt object))))))
  17. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  18. (defun already-logged-in-redirect ()
  19. "Tell the user they are already logged in, and send them to the home page."
  20. (progn
  21. (flash-error (format nil (_ "You are already logged in as: ~a")
  22. (user-name)))
  23. (redirect "/")))
  24. (defun user-exists-register-redirect ()
  25. "Tell the user that email address already is registered, and send them to the registration page."
  26. (progn
  27. (flash-error (_ "A user with that email is already registered."))
  28. (redirect "/user/register" 302)))
  29. (defun login-redirect (email password &optional (target "/"))
  30. "Try to log in using HERMETIC::LOGIN, and display an appropriate message upon success.
  31. TARGET may be set to the redirect target, and is provided from :REQUEST-PATH-INFO in *SESSION*."
  32. (handler-case
  33. (ratify:with-parsed-forms
  34. ((:email email)
  35. (:string password))
  36. (let ((params (list :|username| email :|password| password)))
  37. (login params
  38. (progn
  39. (flash-message (format nil (_ "Hello, ~a!")
  40. (user-name)))
  41. (redirect target))
  42. (progn
  43. (flash-error (_ "Incorrect password."))
  44. (redirect "/user/login"))
  45. (progn
  46. (flash-error (_ "No user corresponding to this email address."))
  47. (redirect "/user/login")))))
  48. (ratify:combined-error (e)
  49. (flash-error e)
  50. (redirect "/user/login" 302))))
  51. (defun register-new-user (name email password)
  52. "Try to register a new user by first SELECTing any matching USERs with EMAIL. If there is a match, use USER-EXISTS-REGISTER-REDIRECT.
  53. Otherwise, create a new user, display the appropriate message, and redirect."
  54. (handler-case
  55. (ratify:with-parsed-forms
  56. ((:string name)
  57. (:string password)
  58. (:email email))
  59. (with-connection (db)
  60. (let ((found (mito:find-dao 'user :email email)))
  61. ;; Try to select this username, if a username is returned, the
  62. ;; user already exists, and display error.
  63. ;;
  64. ;; Otherwise, create the user and add to the database.
  65. ;; Automatically direct to index.html on success.
  66. (if found
  67. (user-exists-register-redirect)
  68. (progn
  69. (if (create-user name email password)
  70. (progn
  71. (flash-message (_ "Success! Please log in."))
  72. (redirect "/user/login"))
  73. (progn
  74. (flash-message (_ "Something went wrong. Try again?"))
  75. (redirect "/user/register"))))))))
  76. (ratify:combined-error (e)
  77. (flash-error e)
  78. (flash-error (_ "Something went wrong. Try again?"))
  79. (redirect "/user/register"))))
  80. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  81. (defun render-user-list (&optional (tpl #P"user/index.html"))
  82. "Helper function. Display User Index page."
  83. (render-with-env tpl
  84. `(:active "/user"
  85. :user-list ,(select-dao 'user))))
  86. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  87. (defroute ("/user" :method :GET) ()
  88. "User Settings Main Index Page."
  89. (with-logged-in-user
  90. (render-with-env #P"user/index.html"
  91. `(:active "/user"))))
  92. (defroute ("/user/login" :method :GET) ()
  93. "User Login Route - require the user to log in."
  94. ;; Always display the USER/LOGIN page
  95. (if (ignore-errors
  96. (logged-in-p))
  97. (already-logged-in-redirect)
  98. (render-with-env #P"user/login.html"
  99. `(:active "/user"))))
  100. (defroute ("/user/login" :method :POST) (&key _parsed)
  101. "User Login POST Route. We use HERMETIC:LOGGED-IN-P to do all the heavy lifting. LOGIN-REDIRECT does the redirect."
  102. (v:info :web "POST /user/login => ~a" _parsed)
  103. ;; (v:info :web "REQUEST-PATH-INFO: ~a SESSION-REQUEST-PATH-INFO: ~a"
  104. ;; (lack.request:request-path-info *request*)
  105. ;; (gethash :request-path-info *session*))
  106. (if (logged-in-p)
  107. (already-logged-in-redirect)
  108. (login-redirect (query-param "email" _parsed)
  109. (query-param "password" _parsed)
  110. (gethash :request-path-info *session*))))
  111. ;; TODO successmessage and errormessages
  112. ;; TODO POST /user/logout ? Currently just GET /user/logout will log you out :P
  113. (defroute "/user/logout" ()
  114. "User Logout Route. GET request. Querying this from anywhere with the LACK.SESSION cookie set will trigger a logout and redirect."
  115. (logout
  116. (flash-message (_ "Logged out."))
  117. (flash-error (_ "You aren't logged in.")))
  118. (redirect "/" 302))
  119. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  120. ;; Register
  121. ;; TODO React To Session Cookie In default.html
  122. ;; Not sure what this is about^
  123. (defroute ("/user/register" :method :GET) ()
  124. "User Registration Main Route. Registration required!"
  125. (let ((logged-in (logged-in-p)))
  126. (if logged-in
  127. (already-logged-in-redirect)
  128. (render-with-env #P"user/register.html"
  129. `(:active "/user")))))
  130. (defroute ("/user/register" :method :POST) (&key _parsed)
  131. "User Registration POST Route. REGISTER-NEW-USER does all the work."
  132. (v:info :web "POST /user/register => ~a" _parsed)
  133. (let ((logged-in (logged-in-p)))
  134. (if logged-in
  135. (already-logged-in-redirect)
  136. (register-new-user (query-param "name" _parsed)
  137. (query-param "email" _parsed)
  138. (query-param "password" _parsed)))))
  139. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  140. (defroute ("/user/:id/delete" :method :POST) (&key id)
  141. "User DELETE Route. ADMIN role only."
  142. (v:info :user "DELETE /user ~d" id)
  143. (handler-case
  144. (ratify:with-parsed-forms
  145. ((:integer id))
  146. (auth (:admin)
  147. (with-connection (db)
  148. (mito:delete-by-values 'user :id id)
  149. "")))
  150. (ratify:combined-error (e)
  151. (flash-error e))))
  152. (defroute ("/user/:id/roles" :method :GET) (&key id)
  153. "User Roles Route. ADMIN role only."
  154. (auth (:admin)
  155. (format nil "~{~s~^ ~}"
  156. (mapcar #'name-of
  157. (user-roles-of (find-dao 'user :id id))))))
  158. (defroute ("/user/admin" :method :GET) ()
  159. "Main User List Route. ADMIN role only."
  160. (auth (:admin)
  161. (render-user-list #P"user/admin.html")))