Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

169 linhas
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*)