summaryrefslogtreecommitdiff
path: root/src/scm/webid-oidc/example-app.scm
diff options
context:
space:
mode:
Diffstat (limited to 'src/scm/webid-oidc/example-app.scm')
-rw-r--r--src/scm/webid-oidc/example-app.scm288
1 files changed, 104 insertions, 184 deletions
diff --git a/src/scm/webid-oidc/example-app.scm b/src/scm/webid-oidc/example-app.scm
index f0fcdd3..d6ef2a0 100644
--- a/src/scm/webid-oidc/example-app.scm
+++ b/src/scm/webid-oidc/example-app.scm
@@ -15,25 +15,30 @@
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
(define-module (webid-oidc example-app)
- #:use-module (webid-oidc client)
+ #:use-module ((webid-oidc client) #:prefix client:)
+ #:use-module ((webid-oidc client accounts) #:prefix client:)
#:use-module (webid-oidc errors)
#:use-module ((webid-oidc cache) #:prefix cache:)
#:use-module (webid-oidc dpop-proof)
#:use-module ((webid-oidc stubs) #:prefix stubs:)
#:use-module ((webid-oidc refresh-token) #:prefix refresh:)
#:use-module ((webid-oidc config) #:prefix cfg:)
+ #:use-module ((webid-oidc jwk) #:prefix jwk:)
#:use-module (web uri)
#:use-module (web client)
+ #:use-module (web request)
#:use-module (web response)
#:use-module (web server)
#:use-module (ice-9 optargs)
#:use-module (ice-9 receive)
#:use-module (srfi srfi-19)
#:use-module (ice-9 i18n)
+ #:use-module (srfi srfi-26)
#:use-module (ice-9 getopt-long)
#:use-module (ice-9 suspendable-ports)
#:use-module (ice-9 textual-ports)
#:use-module (ice-9 rdelim)
+ #:use-module (ice-9 match)
#:use-module (sxml simple)
#:use-module (rnrs bytevectors))
@@ -44,187 +49,102 @@
(car (reverse (string-split text #\|)))
out)))
-(define (enumerate-profiles profiles)
- (define (aux i)
- (when (< i (vector-length profiles))
- (let ((prof (vector-ref profiles i)))
- (format #t (G_ "~a.\t~a, certified by ~a;\n")
- (+ i 1)
- (uri->string (car prof))
- (uri->string (cadr prof))))
- (aux (+ i 1))))
- (aux 0))
-
-(define (enumerate-providers providers)
- (define (aux i)
- (when (< i (vector-length providers))
- (let ((prov (vector-ref providers i)))
- (format #t (G_ "~a – ~a\n")
- (+ i 1)
- (prov)))
- (aux (+ i 1))))
- (aux 0))
-
-(define (select-choice mini maxi question)
- (format #t "~a" question)
- (let* ((line
- (read-line (current-input-port) 'trim))
- (number (false-if-exception (string->number line))))
- (cond
- ((eof-object? line)
- (exit 0))
- ((and (integer? number)
- (>= number mini)
- (<= number maxi))
- number)
- (else
- (format #t (G_ "I’m expecting a number between ~a and ~a.\n")
- mini maxi)
- (select-choice mini maxi question)))))
-
-(define cache-http-get (cache:with-cache))
-
-(define (inner-main-loop http-request)
- (format #t (G_ "Please enter an URI to GET: "))
- (let ((line (read-line (current-input-port) 'trim)))
- (unless (eof-object? line)
- (let ((uri (string->uri line)))
- (receive (response response-body)
- (http-request uri)
- (let ((write-body
- (write-response response (current-output-port))))
- (when (string? response-body)
- (set! response-body (string->utf8 response-body)))
- (when response-body
- (write-response-body write-body response-body)))))
- (inner-main-loop http-request))))
-
-(define (main-loop id-token access-token key)
- (let ((my-http-request
- (make-client id-token access-token key
- #:http-request
- (lambda args
- (format (current-error-port) (G_ "Sending a request: ~s\n") args)
- (apply http-request args)))))
- (inner-main-loop my-http-request)))
-
-(define-public (inner-main)
- (setlocale LC_ALL "")
- (bindtextdomain cfg:package cfg:localedir)
- (textdomain cfg:package)
- (let ((version-sym
- (string->symbol (G_ "command-line|version")))
- (help-sym
- (string->symbol (G_ "comand-line|help"))))
- (let ((options
- (let ((option-spec
- `((,version-sym (single-char #\v) (value #f))
- (,help-sym (single-char #\h) (value #f)))))
- (getopt-long (command-line) option-spec))))
- (cond
- ((option-ref options help-sym #f)
- (format #t (G_ "Usage: ~a [OPTIONS]...
-
-Demonstrate a webid-oidc application.
-
-Options:
- -h, --~a:
- display this help message and exit.
- -v, --~a:
- display the version information (~a) and exit.
-
-Environment variables:
-
- LANG: set the locale. Currently ~a.
-
- XDG_CACHE_HOME: where the seed for the key generator is
-stored. Currently ~a.
-
- XDG_DATA_HOME: where the login credentials are stored. Currently ~a.
-
- HOME: to compute a default value for XDG_CACHE_HOME and
-XDG_DATA_HOME, if missing. Currently ~a.
-
-If you find a bug, send a report to ~a.
-")
- (car (command-line))
- help-sym version-sym
- cfg:version
- (or (getenv "LANG") "")
- (or (getenv "XDG_CACHE_HOME") "")
- (or (getenv "XDG_DATA_HOME") "")
- (or (getenv "HOME") "")
- cfg:package-bugreport))
- ((option-ref options version-sym #f)
- (format #t (G_ "~a version ~a\n")
- cfg:package cfg:version))
- (else
- (let ((profiles (list->vector (list-profiles))))
- (format #t (G_ "First, let’s log in. Here are your options:\n"))
- (enumerate-profiles profiles)
- (format #t (G_ "0.\tLog in with a different identity.\n"))
- (let ((i-profile
- (select-choice
- 0
- (vector-length profiles)
- (G_ "Please indicate your choice number: "))))
- (receive (id-token access-token key)
- (if (eqv? i-profile 0)
- (setup
- (lambda ()
- (format #t (G_ "Please enter your webid, or identity server: "))
- (read-line (current-input-port) 'trim))
- (lambda (providers)
- (cond
- ((null? providers)
- (error "No, this cannot happen."))
- ((null? (cdr providers))
- (car providers))
- (else
- (set! providers (list->vector providers))
- (format #t (G_ "There are different possible identity providers for your webid:\n"))
- (enumerate-providers providers)
- (let ((i-provider
- (select-choice 1 (- (vector-length providers) 1)
- (G_ "Please indicate your choice number: "))))
- (vector-ref providers i-provider)))))
- (lambda (uri)
- (format #t (G_ "Please visit the following URI with a web browser:\n~a\n")
- (uri->string uri))
- (format #t (G_ "Please paste your authorization code: "))
- (read-line (current-input-port) 'trim))
- #:client-id "https://webid-oidc-demo.planete-kraus.eu/example-application#id"
- #:redirect-uri "https://webid-oidc-demo.planete-kraus.eu/authorized"
- #:http-get cache-http-get)
- (let ((profile (vector-ref profiles (- i-profile 1))))
- (let ((webid (car profile))
- (issuer (cadr profile))
- (refresh-token (caddr profile))
- (key (cadddr profile)))
- (login webid issuer refresh-token key #:http-get cache-http-get))))
- (format #t (G_ "Log in success. Keep this identity token for yourself:
-
-~a
-
-Now, you can do authenticated request by presenting the following access token:
-
-~a
-
-and signing DPoP proofs with the following key:
-
-~a
+(define (main)
+ (define (do-the-trick subject issuer)
+ (client:request
+ (client:make-client
+ (string->uri
+ "https://webid-oidc-demo.planete-kraus.eu/example-application#id")
+ (jwk:generate-key #:n-size 2048)
+ (string->uri
+ "https://webid-oidc-demo.planete-kraus.eu/authorized"))
+ subject issuer))
+ (let ((accounts (list->vector (client:read-accounts))))
+ (format #t (G_ "Main menu:\n"))
+ (let enumerate-accounts ((i 0))
+ (when (< i (vector-length accounts))
+ (format #t (G_ "~a. Log in with ~a (issued by ~a): ~a
")
- (stubs:scm->json-string id-token #:pretty #t)
- access-token
- (stubs:scm->json-string key #:pretty #t))
- (main-loop id-token access-token key)))))))))
-
-(define-public (main)
- (with-exception-handler
- (lambda (error)
- (format (current-error-port)
- (G_ "There was an error: ~a\n")
- (error->str error)))
- (lambda ()
- (inner-main))
- #:unwind? #t))
+ (1+ i)
+ (or (let ((subject (client:account-subject (vector-ref accounts i))))
+ (and subject (uri->string subject)))
+ (format #f (G_ "a new user")))
+ (uri->string (client:account-issuer (vector-ref accounts i)))
+ (if (client:account-subject (vector-ref accounts i))
+ (if (client:account-id-token (vector-ref accounts i))
+ (format #f (G_ "status|currently logged in"))
+ (if (client:account-refresh-token (vector-ref accounts i))
+ (format #f (G_ "status|offline (but accessible)"))
+ (format #f (G_ "status|offline (inaccessible)"))))
+ (format #f (G_ "status|not initialized yet"))))
+ (enumerate-accounts (1+ i))))
+ (format #t (G_ "Type a number to log in, prefix it with '-' to delete the account, or type + to create a new account.
+"))
+ (match (read-line (current-input-port) 'trim)
+ ((? string?
+ (= string->number
+ (and (? integer? _)
+ (? (cute >= <> 1) _)
+ (? (cute <= <> (vector-length accounts)))
+ (= (cute - <> 1) choice))))
+ (let ((account (vector-ref accounts choice)))
+ (parameterize
+ ((client:authorization-process
+ ;; There’s a problem with guile continuable
+ ;; exceptions: we can’t handle errors in a handler for
+ ;; continuable exceptions. Until this is clarified, we
+ ;; avoid continuable exceptions.
+ (lambda* (uri #:key issuer)
+ (format (current-error-port) (G_ "Please visit: ~a\n") (uri->string uri))
+ (format (current-error-port) (G_ "Then, paste the authorization code you get:\n"))
+ (read-line (current-input-port) 'trim))))
+ (with-exception-handler
+ (lambda (error)
+ (cond
+ ((client:token-request-failed? error)
+ (format (current-error-port) (G_ "I could not negociate an access token. ~a")
+ (exception-message error))
+ (main))
+ ((client:refresh-token-expired? error)
+ (format (current-error-port) (G_ "The refresh token has expired, it is not possible to use that account offline.\n"))
+ (main))
+ (else
+ (raise-exception error))))
+ (lambda ()
+ (format #t (G_ "Please enter an URI to GET:\n"))
+ (let ((uri (string->uri (read-line (current-input-port) 'trim)))
+ (handler (do-the-trick (client:account-subject account)
+ (client:account-issuer account))))
+ (receive (response response-body)
+ (handler (build-request uri) "")
+ (let ((ad-hoc-port (write-response response (current-output-port))))
+ (unless (response-must-not-include-body? response)
+ (when (string? response-body)
+ (set! response-body (string->utf8 response-body)))
+ (write-response-body ad-hoc-port response-body)))))
+ (format #t "\n")
+ (main))))))
+ ((? string?
+ (= string->number
+ (and (? integer? _)
+ (= (cute - <>)
+ (and (? (cute >= <> 1) _)
+ (? (cute <= <> (vector-length accounts)) _)
+ (= (cute - <> 1) choice))))))
+ ;; Delete
+ (client:delete-account (vector-ref accounts choice))
+ (main))
+ ("+"
+ ;; Create an account
+ (format #t (G_ "Please type your identity provider:\n"))
+ (let ((issuer (read-line (current-input-port) 'trim)))
+ (when (and (string? issuer) (string->uri issuer))
+ (client:save-account
+ (client:make-account #f (string->uri issuer) #f #f #f #f))))
+ (main))
+ ((? eof-object? _)
+ (exit 0))
+ (else
+ (main)))))
+
+(main)