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.

169 lines
5.7KB

  1. ;;;
  2. ;;; See http://diary.wshito.com/comp/lisp/clack/lack-middleware-session/
  3. ;;; for explanation.
  4. ;;;
  5. (ql:quickload 'clack)
  6. (ql:quickload 'lack)
  7. (defun starts-with (str prefix)
  8. (when (>= (length str) (length prefix))
  9. (string= (subseq str 0 (length prefix)) prefix)))
  10. ;;; Middleware to proctect the secure area
  11. ;;; :uidが設定されていない場合,protected-pathにアクセスすると
  12. ;;; redirect関数を呼び出してログインページへリダイレクトする.
  13. (defun secure-mw (redirect protected-path)
  14. (lambda (app)
  15. (lambda (env)
  16. ;; preprocessing
  17. (let* ((url (getf env :path-info))
  18. (session (getf env :lack.session))
  19. (uid (gethash :uid session)))
  20. (if (and (null uid)
  21. (dolist (prefix protected-path)
  22. (when (starts-with url prefix) (return t))))
  23. (progn
  24. ;;当初のアクセス先をセッション変数に保存
  25. (setf (gethash :prev-url session) url)
  26. (funcall redirect))
  27. (funcall app env))))))
  28. ;;; ログインページへリダイレクトするレスポンスを返す.
  29. (defun redirect-to-login-page ()
  30. '(303 (:location "/login") ("")))
  31. (defun get-uid (env)
  32. (gethash :uid (getf env :lack.session)))
  33. (defun get-session-id (env)
  34. (getf (getf env :lack.session.options) :id))
  35. (defun get-change-id (env)
  36. (format nil "~A" (getf (getf env :lack.session.options) :change-id)))
  37. (defun page-header (env)
  38. `("<html><h1>Lack Session Middleware Test</h1>
  39. <h2>--- Login Logout Example ---</h2>
  40. <ul>
  41. <li>Access any directories. Any directories under '<b>/private</b>' needs to be logged in to access.</li>
  42. <li>Session ID: " ,(get-session-id env) "</li>
  43. <li>:change-id = " ,(get-change-id env) "</li>
  44. </ul>
  45. <hr />"))
  46. (defun status (uid)
  47. (if uid
  48. `("<p>You are logged in as " ,uid ". (<a href='/logout'>logout</a>)</p>")
  49. `("<p><a href='/login'>Login</a></p>")))
  50. (defun page-footer ()
  51. '("</html>"))
  52. (defun login-form ()
  53. ;; /auth にuidとpasswdをPOST
  54. '("<p>Use '<b>wshito</b>' for username, '<b>mypass</b>' for password.</p>
  55. <form action='/auth' method='post'>
  56. <p>Username:
  57. <input type='text' name='uname' maxlength='32' autocomplete='OFF' /></p>
  58. <p>Password:
  59. <input type='password' name='passwd' maxlength='32' autocomplete='OFF' /></p>
  60. <p><input type='submit' value='Login' /></p>
  61. </form>"))
  62. ;;; ログインページ
  63. (defparameter *login*
  64. (lambda (env)
  65. (let ((uid (get-uid env)))
  66. `(200 (:content-type "text/html")
  67. ,(append (page-header env)
  68. (if uid
  69. (list "<p>You are already logged in as " uid ".</p>")
  70. (login-form))
  71. (page-footer))))))
  72. ;;; ログアウトページ
  73. (defparameter *logout*
  74. (lambda (env)
  75. (setf (getf (getf env :lack.session.options) :expire) t)
  76. `(200 (:content-type "text/html")
  77. ,(append (page-header env)
  78. (list "<p>You have logged out.</p>")
  79. (page-footer)))))
  80. ;;; 認証関数
  81. (defun authenticate (name password)
  82. (and (string= name "wshito")
  83. (string= password "mypass")))
  84. ;;; :body-parameters内にはPOSTで送られたパラメータが,ドット対
  85. ;;; のリストとして保持されている.この場合だと,
  86. ;;; (("uname" . "wshito") ("passwd" . "mypass"))
  87. (defparameter *auth*
  88. (lambda (env)
  89. (let* ((params (getf env :body-parameters))
  90. (name (cdr (assoc "uname" params :test #'string=)))
  91. (pass (cdr (assoc "passwd" params :test #'string=))))
  92. (if (and (= (length params) 2)
  93. (authenticate name pass))
  94. (let* ((session (getf env :lack.session))
  95. (url (gethash :prev-url session "/")))
  96. (setf (gethash :uid session "/") name)
  97. (setf (getf (getf env :lack.session.options) :change-id) t)
  98. `(303 (:location ,url) ("")))
  99. (redirect-to-login-page)))))
  100. ;;; ログインが必要なprivateエリア
  101. (defparameter *private*
  102. (lambda (env)
  103. (let* ((session (getf env :lack.session))
  104. (uid (gethash :uid session nil))
  105. ;; /privateにmountしているのでpathには/privateが含まれない
  106. (path (concatenate 'string "/private" (getf env :path-info))))
  107. `(200 (:content-type "text/html")
  108. ,(append (page-header env)
  109. (status uid)
  110. (list "<p>Private Area: path = " path "</p>")
  111. (page-footer))))))
  112. ;;;
  113. ;;; Main App
  114. ;;;
  115. (defparameter *sample-app*
  116. (lambda (env)
  117. (let* ((session (getf env :lack.session))
  118. (uid (gethash :uid session))
  119. (path (getf env :path-info)))
  120. (when (null uid) (setf (gethash :prev-url session) path))
  121. `(200 (:content-type "text/html")
  122. ,(append (page-header env)
  123. (status uid)
  124. (list "<p>path = " path "</p>")
  125. (page-footer))))))
  126. ;;;
  127. ;;; Creates Lack Application
  128. ;;; builderチェーンの最後だけが1重lambdaで,それ以外は2重lambda.
  129. ;;; builderされ*app*に渡される内容は外側のlambda式がfuncallで呼びだされた
  130. ;;; 後の結果.外側のlambdaはbuilder時に実行される.
  131. (defparameter *app*
  132. (lack:builder
  133. :session
  134. (secure-mw #'redirect-to-login-page '("/private"))
  135. (:mount "/login" *login*)
  136. (:mount "/auth" *auth*)
  137. (:mount "/logout" *logout*)
  138. (:mount "/private" *private*)
  139. *sample-app*))
  140. ;;;
  141. ;;; Starts the Web server
  142. ;;;
  143. (defparameter *handler*
  144. (clack:clackup *app*))
  145. ;;;
  146. ;;; Stops the Web server
  147. ;;;
  148. ;; (clack:stop *handler*)