git.fiddlerwoaroof.com
Browse code

More refactoring of cl-oid-connect

Various changes to decouple the oid-connect implementation from the
web-server as well as reducing the amount of boilerplate in the various
files.

fiddlerwoaroof authored on 18/10/2015 05:56:16
Showing 3 changed files
... ...
@@ -1,37 +1,38 @@
1 1
 ;;;; cl-oid-connect.lisp
2 2
 ;;;; TODO: Need to refactor out server names!!!
3
+
3 4
 #|
4
-|Copyright (c) 2015 Edward Langley
5
-|All rights reserved.
6
-|
7
-|Redistribution and use in source and binary forms, with or without
8
-|modification, are permitted provided that the following conditions
9
-|are met:
10
-|
11
-|Redistributions of source code must retain the above copyright notice,
12
-|this list of conditions and the following disclaimer.
13
-|
14
-|Redistributions in binary form must reproduce the above copyright
15
-|notice, this list of conditions and the following disclaimer in the
16
-|documentation and/or other materials provided with the distribution.
17
-|
18
-|Neither the name of the project's author nor the names of its
19
-|contributors may be used to endorse or promote products derived from
20
-|this software without specific prior written permission.
21
-|
22
-|THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23
-|"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
-|LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25
-|FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26
-|HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27
-|SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  INCLUDING, BUT NOT LIMITED
28
-|TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29
-|PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30
-|LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31
-|NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32
-|SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
-|
34
-|#
5
+ |Copyright (c) 2015 Edward Langley
6
+ |All rights reserved.
7
+ |
8
+ |Redistribution and use in source and binary forms, with or without
9
+ |modification, are permitted provided that the following conditions
10
+ |are met:
11
+ |
12
+ |Redistributions of source code must retain the above copyright notice,
13
+ |this list of conditions and the following disclaimer.
14
+ |
15
+ |Redistributions in binary form must reproduce the above copyright
16
+ |notice, this list of conditions and the following disclaimer in the
17
+ |documentation and/or other materials provided with the distribution.
18
+ |
19
+ |Neither the name of the project's author nor the names of its
20
+ |contributors may be used to endorse or promote products derived from
21
+ |this software without specific prior written permission.
22
+ |
23
+ |THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ |"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ |LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
+ |FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ |HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ |SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  INCLUDING, BUT NOT LIMITED
29
+ |TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ |PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ |LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ |NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ |SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ |
35
+ |#
35 36
 
36 37
 (in-package :cl-oid-connect)
37 38
 ; Should this be here?
... ...
@@ -60,9 +61,29 @@
60 61
 
61 62
 (defmacro def-route ((url args &key (app *oid*) (method :GET)) &body body)
62 63
   `(setf (ningle:route ,app ,url :method ,method)
63
-         #'(lambda ,args
64
-             (declare (ignorable ,@args))
65
-             ,@body)))
64
+         (lambda ,args
65
+           (declare (ignorable ,@args))
66
+           ,@body)))
67
+
68
+(defun gen-state (len)
69
+  (with-output-to-string (stream)
70
+    (let ((*print-base* 36))
71
+      (loop repeat len
72
+            do (princ (random 36) stream)))))
73
+
74
+(defun valid-state (received-state)
75
+  (let* ((session (context :session))
76
+         (saved-state (gethash :state session)))
77
+    (equal saved-state received-state)))
78
+
79
+(defmacro my-with-context-variables ((&rest vars) &body body)
80
+  "This improves fukamachi's version by permitting the variable to be stored somewhere
81
+   besides the symbol corresponding to the keyword."
82
+  `(symbol-macrolet
83
+       ,(loop for (var key) in (ensure-mapping vars)
84
+              for form = `(context ,(intern (string key) :keyword))
85
+              collect `(,var ,form))
86
+     ,@body))
66 87
 
67 88
 (defparameter *oid* (make-instance 'ningle:<app>))
68 89
 (setf drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*))
... ...
@@ -77,44 +98,14 @@
77 98
                                               (userinfo-endpoint nil :accessor t)
78 99
                                               (auth-scope "openid profile email" :accessor t)
79 100
                                               (redirect-uri nil :accessor t))))
101
+
80 102
 (sheeple:defmessage get-user-info (a b))
81 103
 (sheeple:defmessage get-access-token (a b))
82 104
 
83 105
 (sheeple:defreply get-user-info ((a =endpoint-schema=) (b sheeple:=string=)))
84 106
 (sheeple:defreply get-access-token ((a =endpoint-schema=) (b sheeple:=string=)))
85 107
 
86
-(defparameter *fbook-info* (sheeple:clone =service-info=))
87
-(defparameter *goog-info* (sheeple:clone =service-info=))
88 108
 (defparameter *endpoint-schema* nil)
89
-(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*)))
90
-
91
-(defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*)
92
-          ((auth-endpoint "https://www.facebook.com/dialog/oauth")
93
-           (token-endpoint "https://graph.facebook.com/v2.3/oauth/access_token")
94
-           (userinfo-endpoint "https://graph.facebook.com/v2.3/me")
95
-           (auth-scope "email")
96
-           (redirect-uri  "http://srv2.elangley.org:9090/oidc_callback/facebook")))
97
-
98
-(sheeple:defreply get-access-token ((endpoint-schema *fbook-endpoint-schema*) (code sheeple:=string=))
99
-  (cl-json:decode-json-from-string
100
-    (drakma:http-request (token-endpoint endpoint-schema)
101
-                         :method :post
102
-                         :redirect nil
103
-                         :parameters `(("code" . ,code)
104
-                                       ("client_id" . ,(client-id endpoint-schema))
105
-                                       ("app_id" . ,(client-id endpoint-schema))
106
-                                       ("client_secret" . ,(secret endpoint-schema))
107
-                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
108
-                                       ("grant_type" . "authorization_code")
109
-                                       ("")
110
-                                       ))))
111
-
112
-(sheeple:defreply get-user-info ((endpoint-schema *fbook-endpoint-schema*) (access-token sheeple:=string=))
113
-  (let ((endpoint (userinfo-endpoint endpoint-schema)))
114
-    (cl-json:decode-json-from-string
115
-      (drakma:http-request endpoint
116
-                           :parameters `(("access_token" . ,access-token))))))
117
-
118 109
 (defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
119 110
 (defmacro assoc-cdr (key alist &optional (test '#'eql)) `(cdr (assoc ,key ,alist :test ,test)))
120 111
 
... ...
@@ -145,18 +136,6 @@
145 136
                                      ("redirect_uri" . ,(redirect-uri endpoint-schema))
146 137
                                      ("state" . ,state))))
147 138
 
148
-(defun gen-state (len)
149
-  (with-output-to-string (stream)
150
-    (let ((*print-base* 36))
151
-      (loop repeat len
152
-            do (princ (random 36) stream)))))
153
-
154
-
155
-(defun valid-state (received-state)
156
-  (let* ((session (context :session))
157
-         (saved-state (gethash :state session)))
158
-    (equal saved-state received-state)))
159
-
160 139
 (defmacro auth-entry-point (name endpoint-schema)
161 140
   `(defun ,name (params)
162 141
      (declare (ignore params))
... ...
@@ -169,8 +148,8 @@
169 148
            (if (< rcode 400) `(302 (:location ,(format nil "~a" uri)))
170 149
              content))))))
171 150
 
172
-(flet ((get-code (params) (assoc-cdr "code" params #'equal)))
173
-  (defun run-callback-function (endpoint-schema params get-login-data get-app-user-cb)
151
+(defun run-callback-function (endpoint-schema params get-app-user-cb get-login-data)
152
+  (flet ((get-code (params) (assoc-cdr "code" params #'equal)))
174 153
     (let ((a-t (get-access-token endpoint-schema (get-code params))))
175 154
       (auth-callback-skeleton params (:endpoint-schema endpoint-schema
176 155
                                       :auth-session-vars (accesstoken userinfo idtoken app-user))
... ...
@@ -181,10 +160,14 @@
181 160
                 app-user (funcall get-app-user-cb user-info id-token access-token)))
182 161
         '(302 (:location "/"))))))
183 162
 
184
-(defmacro def-callback-generator (name generator-args callback-args &body body)
185
-  `(defun ,name ,generator-args
186
-     (lambda ,callback-args
187
-       ,@body)))
163
+(defmacro generate-auth-callback (name endpoint-schema params &body body)
164
+  (with-gensyms (get-app-user-cb cb-params)
165
+    `(defun ,name (,get-app-user-cb)
166
+       (lambda (,cb-params)
167
+         (run-callback-function
168
+           ,endpoint-schema ,cb-params ,get-app-user-cb
169
+           (lambda ,params
170
+             ,@body))))))
188 171
 
189 172
 (defmacro reject-when-state-invalid (params &body body)
190 173
   (alexandria:with-gensyms (received-state)
... ...
@@ -208,15 +191,6 @@
208 191
 
209 192
 (define-condition user-not-logged-in (error) ())
210 193
 
211
-(defmacro my-with-context-variables ((&rest vars) &body body)
212
-  "This improves fukamachi's version by permitting the variable to be stored somewhere
213
-   besides the symbol corresponding to the keyword."
214
-  `(symbol-macrolet
215
-       ,(loop for (var key) in (ensure-mapping vars)
216
-              for form = `(context ,(intern (string key) :keyword))
217
-              collect `(,var ,form))
218
-     ,@body))
219
-
220 194
 (defmacro ensure-logged-in (&body body)
221 195
   "Ensure that the user is logged in: otherwise throw the condition user-not-logged-in"
222 196
   (alexandria:with-gensyms (session userinfo)
... ...
@@ -226,13 +200,10 @@
226 200
            (error 'user-not-logged-in)
227 201
            (progn ,@body))))))
228 202
 
229
-(flet
230
-  ((handle-no-user (main-body handler-body)
231
-     `(handler-case
232
-        (ensure-logged-in ,@main-body)
233
-        (user-not-logged-in (e)
234
-                            (declare (ignorable e))
235
-                            ,@handler-body))))
203
+(flet ((handle-no-user (main-body handler-body)
204
+         `(handler-case (ensure-logged-in ,@main-body)
205
+            (user-not-logged-in (e) (declare (ignorable e))
206
+                                ,@handler-body))))
236 207
 
237 208
   (defmacro check-login (&body body)
238 209
     "Returns an HTTP 401 Error if not logged in."
... ...
@@ -245,21 +216,40 @@
245 216
                         (setf next-page (lack.request:request-path-info *request*))
246 217
                         '(302 (:location "/login")))))))
247 218
 
248
-(defun load-facebook-info (loadfrom)
249
-  (with-open-file (fbook-info (truename loadfrom))
250
-    (let* ((data (yason:parse fbook-info))
251
-           (client-id (gethash "client-id" data))
252
-           (secret (gethash "secret" data)))
253
-      (setf (client-id *FBOOK-INFO*) client-id)
254
-      (setf (secret *FBOOK-INFO*) secret))))
255
-
256
-(defun load-google-info (loadfrom)
257
-  (with-open-file (goog-info (truename loadfrom))
258
-    (let* ((data (yason:parse goog-info))
259
-           (client-id (gethash "client-id" data))
260
-           (secret (gethash "secret" data)))
261
-      (setf (client-id *GOOG-INFO*) client-id)
262
-      (setf (secret *GOOG-INFO*) secret))))
219
+(defparameter *fbook-info* (sheeple:clone =service-info=))
220
+(defparameter *goog-info* (sheeple:clone =service-info=))
221
+(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*)))
222
+
223
+(defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*)
224
+          ((auth-endpoint "https://www.facebook.com/dialog/oauth")
225
+           (token-endpoint "https://graph.facebook.com/v2.3/oauth/access_token")
226
+           (userinfo-endpoint "https://graph.facebook.com/v2.3/me")
227
+           (auth-scope "email")
228
+           (redirect-uri  "http://srv2.elangley.org:9090/oidc_callback/facebook")))
229
+
230
+(sheeple:defreply get-access-token ((endpoint-schema *fbook-endpoint-schema*) (code sheeple:=string=))
231
+  (cl-json:decode-json-from-string
232
+    (drakma:http-request (token-endpoint endpoint-schema)
233
+                         :method :post
234
+                         :redirect nil
235
+                         :parameters `(("code" . ,code)
236
+                                       ("client_id" . ,(client-id endpoint-schema))
237
+                                       ("app_id" . ,(client-id endpoint-schema))
238
+                                       ("client_secret" . ,(secret endpoint-schema))
239
+                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
240
+                                       ("grant_type" . "authorization_code")
241
+                                       ("")
242
+                                       ))))
243
+
244
+(sheeple:defreply get-user-info ((endpoint-schema *fbook-endpoint-schema*) (access-token sheeple:=string=))
245
+  (let ((endpoint (userinfo-endpoint endpoint-schema)))
246
+    (cl-json:decode-json-from-string
247
+      (drakma:http-request endpoint
248
+                           :parameters `(("access_token" . ,access-token))))))
249
+
250
+(defun load-provider-secrets (provider-info secrets)
251
+  (setf (client-id provider-info) (assoc-cdr :client-id secrets)
252
+        (secret provider-info) (assoc-cdr :secret secrets)))
263 253
 
264 254
 (defun goog-get-access-token (endpoint-schema code)
265 255
   (cl-json:decode-json-from-string
... ...
@@ -289,49 +279,35 @@
289 279
 (auth-entry-point google-login-entry *goog-endpoint-schema*)
290 280
 (auth-entry-point facebook-login-entry *fbook-endpoint-schema*)
291 281
 
292
-(labels ((get-real-access-token (a-t) (assoc-cdr :access--token a-t))
293
-         (get-id-token (a-t) (cljwt:decode (assoc-cdr :id--token a-t) :fail-if-unsupported nil))
294
-         (get-login-data (a-t)
295
-           (let ((access-token (get-real-access-token a-t)))
296
-             (values access-token
297
-                     (get-user-info *goog-endpoint-schema* access-token)
298
-                     (get-id-token a-t)))))
299
-
300
-  (def-callback-generator google-callback (get-app-user-cb) (params)
301
-    (run-callback-function *goog-endpoint-schema* params #'get-login-data get-app-user-cb)))
302
-
303
-(labels ((get-id-token (a-t) (assoc-cdr :access--token a-t)) ; <-- access--token is not a mistake 
304
-         (get-login-data (a-t)
305
-           (let ((id-token (get-id-token a-t)))
306
-             (values a-t (get-user-info *fbook-endpoint-schema* id-token) id-token))))
307
-
308
-  (def-callback-generator facebook-callback (get-app-user-cb) (params)
309
-    (run-callback-function *fbook-endpoint-schema* params #'get-login-data get-app-user-cb)))
310
-
311
-(defun userinfo-route (params)
312
-  (declare (ignore params))
313
-  (with-context-variables (session)
314
-    (require-login
315
-      (with-endpoints  (gethash :endpoint-schema session)
316
-        (cl-json:encode-json-to-string (gethash :userinfo session))))))
317
-
318
-(defun logout-route (params)
319
-  (declare (ignore params))
320
-  (with-context-variables (session)
321
-    (setf (gethash :userinfo session) nil)
322
-    '(302 (:location "/"))))
323
-
324
-(defun oauth2-login-middleware (app &key google-info facebook-info (login-callback #'identity))
325
-  (load-facebook-info facebook-info)
326
-  (load-goog-endpoint-schema)
327
-  (load-google-info google-info)
328
-  (setf (route app "/userinfo.json" :method :get) #'userinfo-route
329
-        (route app "/logout"  :method :get) #'logout-route
330
-        (route app "/login/google" :method :get) #'google-login-entry
331
-        (route app "/login/facebook" :method :get) #'facebook-login-entry
282
+(generate-auth-callback google-callback *goog-endpoint-schema* (a-t)
283
+  (labels ((get-real-access-token (a-t) (assoc-cdr :access--token a-t))
284
+           (get-id-token (a-t) (cljwt:decode (assoc-cdr :id--token a-t) :fail-if-unsupported nil)))
285
+    (let ((access-token (get-real-access-token a-t)))
286
+      (values access-token
287
+        (get-user-info *goog-endpoint-schema* access-token)
288
+        (get-id-token a-t)))))
289
+
290
+(generate-auth-callback facebook-callback *fbook-endpoint-schema* (a-t)
291
+  (labels ((get-id-token (a-t) (assoc-cdr :access--token a-t)))
292
+    ; ^-- access--token is not a mistake here
293
+    (let ((id-token (get-id-token a-t)))
294
+      (values a-t (get-user-info *fbook-endpoint-schema* id-token) id-token))))
295
+
296
+(defun initialize-oid-connect (facebook-info google-info)
297
+  "Load the Google and Facebook app secrets and initialize Google's openid-configuration
298
+   form its well-known document"
299
+  (load-provider-secrets *fbook-info* facebook-info)
300
+  (load-provider-secrets *goog-info* google-info) 
301
+  (load-goog-endpoint-schema))
302
+
303
+(defun bind-oid-connect-routes (app &optional (login-callback #'identity))
304
+  (setf (route app "/login/google" :method :get) (lambda (params) (google-login-entry params))
305
+        (route app "/login/facebook" :method :get) (lambda (params) (facebook-login-entry params))
332 306
         (route app "/oidc_callback/google" :method :get) (google-callback login-callback)
333
-        (route app "/oidc_callback/facebook" :method :get) (facebook-callback login-callback))
334
-  (lambda (app) (lambda (env) (funcall app env))))
307
+        (route app "/oidc_callback/facebook" :method :get) (facebook-callback login-callback)))
308
+
309
+(defmacro setup-oid-connect (app args &body callback)
310
+  `(bind-oid-connect-routes ,app (lambda ,args ,@callback)))
335 311
 
336 312
 (defmacro redirect-if-necessary (sessionvar &body body)
337 313
   (with-gensyms (session)
... ...
@@ -1,4 +1,6 @@
1 1
 (in-package :cl-user)
2
+(ql:quickload :clack-middleware-postmodern)
3
+
2 4
 (ql:quickload :cl-markup)
3 5
 (ql:quickload :cl-oid-connect)
4 6
 (ql:quickload :colors)
... ...
@@ -25,7 +27,7 @@
25 27
 (load "rss.lisp")
26 28
 
27 29
 (defpackage :whitespace
28
-  (:use #:cl #:whitespace.utils #:whitespace.feeds.rss #:whitespace.tables))
30
+  (:use #:cl #:anaphora #:whitespace.utils #:whitespace.feeds.rss #:whitespace.tables))
29 31
 
30 32
 (in-package plump-dom)
31 33
 
... ...
@@ -141,18 +143,18 @@
141 143
 
142 144
 ; ; ;  Ultimately, this will only serialize the feed if the client
143 145
 (cl-oid-connect:def-route ("/feeds/add" (params) :method :post :app *app*)
144
-  (ningle.context:with-context-variables (session) 
146
+  (ningle.context:with-context-variables (session)
145 147
     (let ((user-info (gethash :app-user session))
146 148
           (result '(302 (:location "/")))
147
-          (api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes")) 
148
-          (url (cl-oid-connect:assoc-cdr "url" params 'string=)) 
149
+          (api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes"))
150
+          (url (cl-oid-connect:assoc-cdr "url" params 'string=))
149 151
           (plump-parser:*tag-dispatchers* plump-parser:*xml-tags*))
150 152
       (cl-oid-connect:require-login
151 153
         (when (neither-null params user-info)
152 154
           (handler-case
153 155
             (let* ((doc (plump:parse (drakma:http-request url)))
154 156
                    (uid (slot-value user-info 'id)))
155
-              (multiple-value-bind (added-feed dao-feed) (store-feed doc) 
157
+              (multiple-value-bind (added-feed dao-feed) (store-feed doc)
156 158
                 (subscribe-to-feed uid (slot-value dao-feed 'id))
157 159
                 (when api
158 160
                   (setf result `(200 (:Content-Type "application/json") ,(jsonapi-encoder t added-feed))))))
... ...
@@ -182,38 +184,7 @@
182 184
                                                           collect (elt *feeds* x)))))
183 185
         (base-template-f)))))
184 186
 
185
-(defun login-callback (userinfo &rest args)
186
-  (declare (ignore args))
187
-  (postmodern:with-transaction ()
188
-    (let* ((received-id (anaphora:aif (cl-oid-connect:assoc-cdr :id userinfo)
189
-                          anaphora:it
190
-                          (cl-oid-connect:assoc-cdr :sub userinfo)))
191
-           (db-user (car (postmodern:select-dao 'reader_user (:= :foreign-id received-id)))))
192
-      (if (not (null db-user))
193
-        db-user
194
-        (progn
195
-          (let ((name (cl-oid-connect:assoc-cdr :name userinfo))
196
-                (first-name (anaphora:aif (cl-oid-connect:assoc-cdr :first--name userinfo)
197
-                              anaphora:it
198
-                              (cl-oid-connect:assoc-cdr :given--name userinfo)))
199
-                (last-name (anaphora:aif (cl-oid-connect:assoc-cdr :last--name userinfo)
200
-                             anaphora:it
201
-                             (cl-oid-connect:assoc-cdr :family--name userinfo)))
202
-                (email (cl-oid-connect:assoc-cdr :email userinfo))
203
-                (gender (cl-oid-connect:assoc-cdr :gender userinfo))
204
-                (link (anaphora:aif (cl-oid-connect:assoc-cdr :link userinfo)
205
-                        anaphora:it
206
-                        (cl-oid-connect:assoc-cdr :profile userinfo)))
207
-                (locale (cl-oid-connect:assoc-cdr :locale userinfo)))
208
-            (postmodern:make-dao 'reader_user
209
-                                 :foreign-id received-id
210
-                                 :first-name first-name
211
-                                 :last-name last-name
212
-                                 :name name
213
-                                 :email email
214
-                                 :gender gender
215
-                                 :link link
216
-                                 :locale locale)))))))
187
+
217 188
 
218 189
 (cl-oid-connect:def-route ("/demo" (params) :app *app*)
219 190
   (base-template-f t))
... ...
@@ -325,14 +296,45 @@
325 296
   (colors:let-palette (colors:invert-palette (make-instance 'colors:palette))
326 297
     (eval '(get-theme-css))))
327 298
 
328
-(defparameter oid-mw
329
-    (cl-oid-connect:oauth2-login-middleware
330
-      *app*
331
-      :facebook-info (truename "~/github_repos/cl-oid-connect/facebook-secrets.json")
332
-      :google-info (truename "~/github_repos/cl-oid-connect/google-secrets.json")
333
-      :login-callback #'login-callback))
299
+(cl-oid-connect:def-route ("/userinfo.json" (params) :app *app*)
300
+  (declare (ignore params))
301
+  (ningle:with-context-variables (session)
302
+    (cl-oid-connect:require-login
303
+      (cl-oid-connect::with-endpoints (gethash :endpoint-schema session)
304
+        `(200 (:content-type "application/json") ,(cl-json:encode-json-to-string (gethash :userinfo session)))))))
334 305
 
335
-(ql:quickload :clack-middleware-postmodern)
306
+(cl-oid-connect:def-route ("/logout" (params) :app *app*)
307
+  (declare (ignore params))
308
+  (ningle:with-context-variables (session)
309
+    (setf (gethash :userinfo session) nil)
310
+    '(302 (:location "/"))))
311
+
312
+(defun assoc-cdr-alternatives (alist alt1 alt2 &optional (test #'eql))
313
+  (aif (cl-oid-connect:assoc-cdr alt1 alist test)
314
+    it
315
+    (cl-oid-connect:assoc-cdr alt2 alist test)))
316
+
317
+(cl-oid-connect::setup-oid-connect *app* (userinfo &rest args)
318
+  (declare (ignore args) (optimize (speed 0) (safety 3) (debug 3)))
319
+  (flet ((get-received-id (userinfo) (assoc-cdr-alternatives userinfo :id :sub))
320
+         (get-db-user (received-id) (car (postmodern:select-dao 'reader_user (:= :foreign-id received-id)))) 
321
+         (get-first-name (userinfo) (assoc-cdr-alternatives userinfo :first--name :given--name))
322
+         (get-last-name (userinfo) (assoc-cdr-alternatives userinfo :last--name :family--name))
323
+         (get-link (userinfo) (assoc-cdr-alternatives userinfo :link :profile)))
324
+
325
+    (postmodern:with-transaction ()
326
+      (let ((received-id (get-received-id userinfo)))
327
+        (aif (get-db-user received-id) it
328
+          (postmodern:make-dao
329
+            'reader_user
330
+            :foreign-id received-id
331
+            :first-name (get-first-name userinfo)
332
+            :last-name (get-last-name userinfo)
333
+            :name (cl-oid-connect:assoc-cdr :name userinfo)
334
+            :email (cl-oid-connect:assoc-cdr :email userinfo)
335
+            :gender (cl-oid-connect:assoc-cdr :gender userinfo)
336
+            :link (get-link userinfo)
337
+            :locale (cl-oid-connect:assoc-cdr :locale userinfo)))))))
336 338
 
337 339
 (defun update-feed (url)
338 340
   (with-whitespace-db
... ...
@@ -382,6 +384,9 @@
382 384
   (defun stop () (clack:stop (pop handler)))
383 385
 
384 386
   (defun start (&optional tmp)
387
+    (cl-oid-connect:initialize-oid-connect
388
+      (ubiquitous:value 'facebook 'secrets)
389
+      (ubiquitous:value 'google 'secrets))
385 390
     (let ((server (if (> (length tmp) 1)
386 391
                     (intern (string-upcase (elt tmp 1)) 'keyword)
387 392
                     :hunchentoot)))
... ...
@@ -400,6 +405,6 @@
400 405
   (defun restart-clack ()
401 406
     (do () ((null handler)) (stop))
402 407
     (start)))
403
- 
408
+
404 409
 
405 410
 ; vim: foldmethod=marker foldmarker=(,) foldminlines=3 :
... ...
@@ -27,5 +27,6 @@
27 27
     #:assoc-cdr
28 28
     #:session ; private!!
29 29
     #:vars-to-symbol-macrolets
30
+    #:initialize-oid-connect
30 31
     ))
31 32