summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-04-18 19:27:50 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-06-05 16:59:44 +0200
commit68221cd57d155c33a207cc89ced90b48ef5b1699 (patch)
tree20296938fe3d1e47f9304a8bffe1a0f4adb62d08
parent4c5e746792dbee40252bb728c1f8debc516210f0 (diff)
Negociate a token (client)
-rw-r--r--doc/webid-oidc.texi119
-rw-r--r--po/fr.po297
-rw-r--r--po/webid-oidc.pot264
-rw-r--r--src/scm/webid-oidc/Makefile.am6
-rw-r--r--src/scm/webid-oidc/cache.scm2
-rw-r--r--src/scm/webid-oidc/client.scm487
-rw-r--r--src/scm/webid-oidc/errors.scm68
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/client-authorization.scm118
-rw-r--r--tests/client-token.scm121
10 files changed, 1217 insertions, 269 deletions
diff --git a/doc/webid-oidc.texi b/doc/webid-oidc.texi
index dda97bd..55a92d9 100644
--- a/doc/webid-oidc.texi
+++ b/doc/webid-oidc.texi
@@ -51,6 +51,7 @@ Free Documentation License''
* Caching on server side::
* Running an Identity Provider::
* Running a Resource Server::
+* Running a client::
* Exceptional conditions::
* GNU Free Documentation License::
* Index::
@@ -515,6 +516,104 @@ the subject of the access token. If an error happens, it is thrown;
the function always returns a valid URI.
@end deffn
+@node Running a client
+@chapter Running a client
+
+To run a client, you need to proceed in two steps. First, acquire an
+OIDC ID token and an access token from the identity provider, and then
+present the access token and a proof of possession of the linked key
+in each request, in a DPoP HTTP header.
+
+The first operation is performed by the @emph{(webid-oidc client)}
+module.
+
+@deffn function authorize @var{host/webid} @var{#client-id} @var{#redirect-uri} @var{[#state]} @var{[#http-get]}
+The user enters a valid webid or a host name, and then this function
+will query it (with the @var{http-get} parameter, by default the web
+client from @emph{(web client)}) to determine the authorization
+endpoint. The function will return an alist of authorization URIs,
+indexed by approved identity provider URIs, that the user should
+browse with a traditional web browser.
+
+Each application should have its own webid, or in that case
+@var{client-id}, that can be dereferenced by the identity provider.
+
+Once the user has given authorization, the user’s agent will be
+redirected to @var{redirect-uri}, with the authorization code as a GET
+parameter. It is possible to pass a @var{state}, but this is optional.
+@end deffn
+
+Once the client gets the authorization code, it is necessary to create
+an access token and ID token.
+
+@deffn function token @var{host} @var{client-key} @var{[#authorization-code]} @var{[#refresh-token]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+Trade an @var{authorization-code}, or a @var{refresh-token}, for an ID
+token and an access token bound to the @var{client-key} issued by
+@var{host}, the identity provider.
+
+You can override the HTTP client used (@var{http-get} and
+@var{http-post}), and how to compute the time (@var{current-time}).
+@end deffn
+
+In an application, you would have a list of profiles in XDG_DATA_HOME,
+consisting of triples (webid, issuer, refresh token).
+
+@deffn function list-profiles @var{[#dir]}
+Read the list of available profiles. Returns a list of triples, webid,
+issuer, reresh token.
+
+By default, this function will look for the profiles file in
+@var{XDG_DATA_HOME}. You can bypass it by providing the @var{#dir}
+optional keyword argument.
+@end deffn
+
+@deffn function setup @var{get-host/webid} @var{choose-provider} @var{browse-authorization-uri} @var{#client-id} @var{#redirect-uri} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+Negociate a refresh token, and save it. The function returns 3 values:
+the decoded ID token pyload, the encoded access token and the key
+pair.
+
+The @var{get-host/webid} thunk should ask the user’s webid or identity
+provider, and return it. @var{choose-provider} is called with a list
+of possible identity providers as host names (strings), and the user
+should choose one. The chosen one is returned. Finally,
+@var{browse-authorization-uri} should ask or let the user browse an
+URI as its argument, and return the authorization code taken from the
+redirect URI.
+
+The refresh token is saved to disk, as a profile, in
+XDG_DATA_HOME. Pass the optional @var{#dir} keyword argument to
+override the location.
+
+You need to set @var{client-id} to the public webid of the app, and
+@var{redirect-uri} to one of the approved redirection URIs for the
+application ID.
+@end deffn
+
+@deffn function login @var{webid} @var{issuer} @var{refresh-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+If you have already a known profile, you can use it to automatically
+log in. This function might update the refresh token if it changed, so
+you can again set @var{#dir}. Please note that the @var{refresh-token}
+is bound to the client @var{key} on server side, so you must always
+use the same @var{key}.
+@end deffn
+
+@deffn function refresh @var{id-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#current-time]}
+If you have an ID token bound to a known profile, this helper function
+will look up the associated refresh token and log in.
+@end deffn
+
+@deffn function make-client @var{id-token} @var{access-token} @var{key} @var{[#dir]} @var{[#http-get]} @var{[#http-post]} @var{[#http-request]} @var{[#current-time]}
+Return a replacement of @code{http-request} from @emph{(web client)},
+that automatically signs requests and refresh the tokens when needed.
+
+@var{#http-get} and @var{#http-post} are only used to refresh the
+tokens, while @var{#http-request} is used as a back-end for the
+requests.
+
+@var{#current-time} is set to a thunk that returns the time. It is
+used to issue DPoP proofs.
+@end deffn
+
@node Exceptional conditions
@chapter Exceptional conditions
@@ -913,6 +1012,26 @@ The token request forgot to put a refresh token with the request.
provider.
@end deftp
+@deftp {exception type} &no-provider-candidates @var{webid} @var{causes}
+The @var{webid} cannot be certified by any identity providers. The
+@var{causes} alist indicates an error for each candidates.
+@end deftp
+
+@deftp {exception type} &neither-identity-provider-nor-webid @var{uri} @var{why-not-identity-provider} @var{why-not-webid}
+The @var{uri} you passed to get an authorization code is neither an
+identity provider (because @var{why-not-identity-provider}) nor a
+webid (because @var{why-not-webid}).
+@end deftp
+
+@deftp {exception type} &token-request-failed @var{cause}
+The token request failed on the server.
+@end deftp
+
+@deftp {exception type} &profile-not-found @var{webid} @var{iss} @var{dir}
+The @var{webid}, as certified by @var{iss}, cannot be refreshed
+because we don’t have a refresh token stored in @var{dir}.
+@end deftp
+
@node GNU Free Documentation License
@appendix GNU Free Documentation License
diff --git a/po/fr.po b/po/fr.po
index 222f4cf..d2ee64b 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: webid-oidc 0.0.0\n"
"Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n"
-"POT-Creation-Date: 2021-06-05 16:20+0200\n"
+"POT-Creation-Date: 2021-06-05 16:21+0200\n"
"PO-Revision-Date: 2021-06-05 11:07+0200\n"
"Last-Translator: Vivien Kraus <vivien@planete-kraus.eu>\n"
"Language-Team: French <vivien@planete-kraus.eu>\n"
@@ -126,101 +126,101 @@ msgstr "Utilisation : generate-random [NOMBRE D'OCTETS]\n"
msgid "Usage: generate-key [NUMBER OF BITS | CURVE]\n"
msgstr "Utilisation : generate-key [NOMBRE DE BITS | COURBE]\n"
-#: src/scm/webid-oidc/errors.scm:839
+#: src/scm/webid-oidc/errors.scm:881
msgid "that’s how it is"
msgstr "c’est comme ça"
-#: src/scm/webid-oidc/errors.scm:844
+#: src/scm/webid-oidc/errors.scm:886
#, scheme-format
msgid "the value ~s is not a base64 string (because ~a)"
msgstr "la valeur ~s n’est pas une chaîne base64 (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:847
+#: src/scm/webid-oidc/errors.scm:889
#, scheme-format
msgid "the value ~s is not JSON (because ~a)"
msgstr "la valeur ~s n’est pas du JSON (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:850
+#: src/scm/webid-oidc/errors.scm:892
#, scheme-format
msgid "the value ~s is not Turtle (because ~a)"
msgstr "la valeur ~s n’est pas du Turtle (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:853
+#: src/scm/webid-oidc/errors.scm:895
#, scheme-format
msgid "the value ~s does not identify an elleptic curve"
msgstr "la valeur ~s n’identifie pas une courbe elliptique"
-#: src/scm/webid-oidc/errors.scm:858
+#: src/scm/webid-oidc/errors.scm:900
#, scheme-format
msgid "the value ~s does not identify a JWK (because ~a)"
msgstr "la valeur ~s n’identifie pas une JWK (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:860
+#: src/scm/webid-oidc/errors.scm:902
#, scheme-format
msgid "the value ~s does not identify a JWK"
msgstr "la valeur ~s n’identifie pas une JWK"
-#: src/scm/webid-oidc/errors.scm:865
+#: src/scm/webid-oidc/errors.scm:907
#, scheme-format
msgid "the value ~s does not identify a public JWK (because ~a)"
msgstr "la valeur ~s n’identifie pas une JWK publique (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:867
+#: src/scm/webid-oidc/errors.scm:909
#, scheme-format
msgid "the value ~s does not identify a public JWK"
msgstr "la valeur ~s n’identifie pas une JWK publique"
-#: src/scm/webid-oidc/errors.scm:872
+#: src/scm/webid-oidc/errors.scm:914
#, scheme-format
msgid "the value ~s does not identify a private JWK (because ~a)"
msgstr "la valeur ~s n’identifie pas une JWK privée (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:874
+#: src/scm/webid-oidc/errors.scm:916
#, scheme-format
msgid "the value ~s does not identify a private JWK"
msgstr "la valeur ~s n’identifie pas une JWK privée"
-#: src/scm/webid-oidc/errors.scm:879
+#: src/scm/webid-oidc/errors.scm:921
#, scheme-format
msgid "the value ~s does not identify a JWKS (because ~a)"
msgstr "la valeur ~s n’identifie pas un JWKS (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:881
+#: src/scm/webid-oidc/errors.scm:923
#, scheme-format
msgid "the value ~s does not identify a JWKS"
msgstr "la valeur ~s n’identifie pas un JWKS"
-#: src/scm/webid-oidc/errors.scm:884
+#: src/scm/webid-oidc/errors.scm:926
#, scheme-format
msgid "the value ~s does not identify a hash algorithm"
msgstr "la valeur ~s n’identifie pas un algorithme de hachage"
-#: src/scm/webid-oidc/errors.scm:887
+#: src/scm/webid-oidc/errors.scm:929
#, scheme-format
msgid "the value ~s is not an alist or misses key ~s"
msgstr "la valeur ~s n’est pas une alist ou il manque la clé ~s"
-#: src/scm/webid-oidc/errors.scm:890
+#: src/scm/webid-oidc/errors.scm:932
#, scheme-format
msgid "the value ~s is not a JWS header (because ~a)"
msgstr "la valeur ~s n’est pas un header JWS (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:893
+#: src/scm/webid-oidc/errors.scm:935
#, scheme-format
msgid "the value ~s is not a JWS payload (because ~a)"
msgstr "la valeur ~s n’est pas un contenu JWS (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:896
+#: src/scm/webid-oidc/errors.scm:938
#, scheme-format
msgid "the value ~s is not a JWS (because ~a)"
msgstr "la valeur ~s n’est pas un JWS (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:899
+#: src/scm/webid-oidc/errors.scm:941
#, scheme-format
msgid "the string ~s cannot be split in 3 parts with ~s"
msgstr "la chaîne ~s ne peut pas être découpée en 3 parties avec ~s"
-#: src/scm/webid-oidc/errors.scm:902
+#: src/scm/webid-oidc/errors.scm:944
#, scheme-format
msgid ""
"all key candidates failed to verify signature ~s with algorithm ~s and "
@@ -229,17 +229,17 @@ msgstr ""
"aucune clé candidate n’a pu vérifier la signature ~s avec l’algorithme ~s et "
"le contenu ~a (il y en avait ~a : ~s)"
-#: src/scm/webid-oidc/errors.scm:905
+#: src/scm/webid-oidc/errors.scm:947
#, scheme-format
msgid "I cannot decode JWS ~a (because ~a)"
msgstr "je n’ai pas pu décoder le JWS encodé par ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:908
+#: src/scm/webid-oidc/errors.scm:950
#, scheme-format
msgid "I cannot encode JWS ~a (because ~a)"
msgstr "je n’ai pas pu encoder le JWS ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:911
+#: src/scm/webid-oidc/errors.scm:953
#, scheme-format
msgid ""
"the server request unexpectedly failed with code ~a and reason phrase ~s"
@@ -247,338 +247,338 @@ msgstr ""
"la requête au serveur a échoué de façon inattendue avec un code ~a et une "
"raison ~s"
-#: src/scm/webid-oidc/errors.scm:916
+#: src/scm/webid-oidc/errors.scm:958
#, scheme-format
msgid "the header ~a should not have the value ~s"
msgstr "l’en-tête ~a ne devrait pas avoir la valeur ~s"
-#: src/scm/webid-oidc/errors.scm:918
+#: src/scm/webid-oidc/errors.scm:960
#, scheme-format
msgid "the header ~a should be present"
msgstr "l’en-tête ~a devrait être présent"
-#: src/scm/webid-oidc/errors.scm:921
+#: src/scm/webid-oidc/errors.scm:963
#, scheme-format
msgid "the server response wasn't expected: ~s (because ~a)"
msgstr "la réponse du serveur est inattendue : ~s (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:927
+#: src/scm/webid-oidc/errors.scm:969
#, scheme-format
msgid "the value ~s is not an OIDC configuration (because ~a)"
msgstr "la valeur ~s n’est pas une configuration OIDC (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:932
+#: src/scm/webid-oidc/errors.scm:974
#, scheme-format
msgid "the webid field is incorrect: ~s"
msgstr "le champ webid est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:933
+#: src/scm/webid-oidc/errors.scm:975
msgid "the webid field is missing"
msgstr "le champ webid est manquant"
-#: src/scm/webid-oidc/errors.scm:937
+#: src/scm/webid-oidc/errors.scm:979
#, scheme-format
msgid "the sub field is incorrect: ~s"
msgstr "le champ sub est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:938
+#: src/scm/webid-oidc/errors.scm:980
msgid "the sub field is missing"
msgstr "le champ sub est manquant"
-#: src/scm/webid-oidc/errors.scm:942
+#: src/scm/webid-oidc/errors.scm:984
#, scheme-format
msgid "the iss field is incorrect: ~s"
msgstr "le champ iss est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:943
+#: src/scm/webid-oidc/errors.scm:985
msgid "the iss field is missing"
msgstr "le champ iss est manquant"
-#: src/scm/webid-oidc/errors.scm:947
+#: src/scm/webid-oidc/errors.scm:989
#, scheme-format
msgid "the aud field is incorrect: ~s"
msgstr "le champ aud est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:948
+#: src/scm/webid-oidc/errors.scm:990
msgid "the aud field is missing"
msgstr "le champ aud est manquant"
-#: src/scm/webid-oidc/errors.scm:952
+#: src/scm/webid-oidc/errors.scm:994
#, scheme-format
msgid "the iat field is incorrect: ~s"
msgstr "le champ iat est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:953
+#: src/scm/webid-oidc/errors.scm:995
msgid "the iat field is missing"
msgstr "le champ iat est manquant"
-#: src/scm/webid-oidc/errors.scm:957
+#: src/scm/webid-oidc/errors.scm:999
#, scheme-format
msgid "the exp field is incorrect: ~s"
msgstr "le champ exp est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:958
+#: src/scm/webid-oidc/errors.scm:1000
msgid "the exp field is missing"
msgstr "le champ exp est manquant"
-#: src/scm/webid-oidc/errors.scm:962
+#: src/scm/webid-oidc/errors.scm:1004
#, scheme-format
msgid "the cnf/jkt field is incorrect: ~s"
msgstr "le champ cnf/jkt est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:963
+#: src/scm/webid-oidc/errors.scm:1005
msgid "the cnf/jkt field is missing"
msgstr "le champ cnf/jkt est manquant"
-#: src/scm/webid-oidc/errors.scm:967
+#: src/scm/webid-oidc/errors.scm:1009
#, scheme-format
msgid "the client-id field is incorrect: ~s"
msgstr "le champ client-id est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:968
+#: src/scm/webid-oidc/errors.scm:1010
msgid "the client-id field is missing"
msgstr "le champ client-id est manquant"
-#: src/scm/webid-oidc/errors.scm:972
+#: src/scm/webid-oidc/errors.scm:1014
#: src/scm/webid-oidc/authorization-page-unsafe.scm:133
#, scheme-format
msgid "the redirect_uris field is incorrect: ~s"
msgstr "le champ redirect_uris est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:973
+#: src/scm/webid-oidc/errors.scm:1015
#: src/scm/webid-oidc/authorization-page-unsafe.scm:134
msgid "the redirect_uris field is missing"
msgstr "le champ redirect_uris est manquant"
-#: src/scm/webid-oidc/errors.scm:977
+#: src/scm/webid-oidc/errors.scm:1019
#, scheme-format
msgid "the typ field is incorrect: ~s"
msgstr "le champ typ est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:978
+#: src/scm/webid-oidc/errors.scm:1020
msgid "the typ field is missing"
msgstr "le champ typ est manquant"
-#: src/scm/webid-oidc/errors.scm:982
+#: src/scm/webid-oidc/errors.scm:1024
#, scheme-format
msgid "the jwk field is incorrect: ~s (because ~a)"
msgstr "le champ jwk est incorrect : ~s (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:984
+#: src/scm/webid-oidc/errors.scm:1026
msgid "the jwk field is missing"
msgstr "le champ jwk est manquant"
-#: src/scm/webid-oidc/errors.scm:988
+#: src/scm/webid-oidc/errors.scm:1030
#, scheme-format
msgid "the jti field is incorrect: ~s"
msgstr "le champ jti est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:989
+#: src/scm/webid-oidc/errors.scm:1031
msgid "the jti field is missing"
msgstr "le champ jti est manquant"
-#: src/scm/webid-oidc/errors.scm:993
+#: src/scm/webid-oidc/errors.scm:1035
#, scheme-format
msgid "the nonce field is incorrect: ~s"
msgstr "le champ nonce est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:994
+#: src/scm/webid-oidc/errors.scm:1036
msgid "the nonce field is missing"
msgstr "le champ nonce est manquant"
-#: src/scm/webid-oidc/errors.scm:998
+#: src/scm/webid-oidc/errors.scm:1040
#, scheme-format
msgid "the htm field is incorrect: ~s"
msgstr "le champ htm est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:999
+#: src/scm/webid-oidc/errors.scm:1041
msgid "the htm field is missing"
msgstr "le champ htm est manquant"
-#: src/scm/webid-oidc/errors.scm:1003
+#: src/scm/webid-oidc/errors.scm:1045
#, scheme-format
msgid "the htu field is incorrect: ~s"
msgstr "le champ htu est incorrect : ~s"
-#: src/scm/webid-oidc/errors.scm:1004
+#: src/scm/webid-oidc/errors.scm:1046
msgid "the htu field is missing"
msgstr "le champ htu est manquant"
-#: src/scm/webid-oidc/errors.scm:1006
+#: src/scm/webid-oidc/errors.scm:1048
#, scheme-format
msgid "~s is not an access token (because ~a)"
msgstr "~s n’est pas un jeton d’accès (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1009
+#: src/scm/webid-oidc/errors.scm:1051
#, scheme-format
msgid "~s is not an access token header (because ~a)"
msgstr "~s n’est pas un en-tête de jeton d’accès (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1012
+#: src/scm/webid-oidc/errors.scm:1054
#, scheme-format
msgid "~s is not an access token payload (because ~a)"
msgstr "~s n’est pas un contenu de jeton d’accès (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1015
+#: src/scm/webid-oidc/errors.scm:1057
#, scheme-format
msgid "~s is not a DPoP proof (because ~a)"
msgstr "~s n’est pas une preuve DPoP (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1018
+#: src/scm/webid-oidc/errors.scm:1060
#, scheme-format
msgid "~s is not a DPoP proof header (because ~a)"
msgstr "~s n’est pas un en-tête de preuve DPoP (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1021
+#: src/scm/webid-oidc/errors.scm:1063
#, scheme-format
msgid "~s is not a DPoP proof payload (because ~a)"
msgstr "~s n’est pas un contenu de preuve DPoP (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1024
+#: src/scm/webid-oidc/errors.scm:1066
#, scheme-format
msgid "I cannot fetch the issuer configuration of ~a (because ~a)"
msgstr ""
"je n’ai pas pu récupérer la configuration de l’émetteur ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1031
+#: src/scm/webid-oidc/errors.scm:1073
#, scheme-format
msgid "I cannot fetch the JWKS of ~a at ~a (because ~a)"
msgstr "je n’ai pas pu récupérer le JWKS de ~a à ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1042
+#: src/scm/webid-oidc/errors.scm:1084
#, scheme-format
msgid "the HTTP method is signed for ~s, but ~s was requested"
msgstr "la méthode HTTP a été signée pour ~s, mais ~s a été demandé"
-#: src/scm/webid-oidc/errors.scm:1045
+#: src/scm/webid-oidc/errors.scm:1087
#, scheme-format
msgid "the HTTP uri is signed for ~a, but ~a was requested"
msgstr "l’uri HTTP a été signé pour ~a, mais ~a a été demandé"
-#: src/scm/webid-oidc/errors.scm:1048
+#: src/scm/webid-oidc/errors.scm:1090
#, scheme-format
msgid "the date is ~a, but the DPoP proof is signed in the future at ~a"
msgstr "la date est ~a, mais la preuve DPoP a été signée dans le futur à ~a"
-#: src/scm/webid-oidc/errors.scm:1052
+#: src/scm/webid-oidc/errors.scm:1094
#, scheme-format
msgid "the date is ~a, but the DPoP proof was signed too long ago at ~a"
msgstr ""
"la date est ~a, mais la preuve DPoP a été signée il y a trop longtemps à ~a"
-#: src/scm/webid-oidc/errors.scm:1061
+#: src/scm/webid-oidc/errors.scm:1103
#, scheme-format
msgid "the key ~s does not hash to ~a"
msgstr "la clé ~s ne donne pas un hash de ~a"
-#: src/scm/webid-oidc/errors.scm:1063
+#: src/scm/webid-oidc/errors.scm:1105
#, scheme-format
msgid "the key confirmation of ~s failed (because ~a)"
msgstr "la confirmation de clé de ~s a échoué (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1065
+#: src/scm/webid-oidc/errors.scm:1107
#, scheme-format
msgid "the key confirmation of ~s failed"
msgstr "la confirmation de la clé ~s a échoué"
-#: src/scm/webid-oidc/errors.scm:1067
+#: src/scm/webid-oidc/errors.scm:1109
#, scheme-format
msgid "the jti ~s has already been found (because ~a)"
msgstr "le jti ~s a déjà été trouvé (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1070
+#: src/scm/webid-oidc/errors.scm:1112
#, scheme-format
msgid "I cannot decode ~s as an access token (because ~a)"
msgstr "je n’ai pas pu décoder ~s comme jeton d’accès (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1073
+#: src/scm/webid-oidc/errors.scm:1115
#, scheme-format
msgid "I cannot encode ~s as an access token with key ~s (because ~a)"
msgstr ""
"je n’ai pas pu encoder ~s comme un jeton d’accès avec la clé ~s (parce que "
"~a)"
-#: src/scm/webid-oidc/errors.scm:1076
+#: src/scm/webid-oidc/errors.scm:1118
#, scheme-format
msgid "I cannot decode ~s as a DPoP proof (because ~a)"
msgstr "je n’ai pas pu décoder ~s comme preuve DPoP (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1079
+#: src/scm/webid-oidc/errors.scm:1121
#, scheme-format
msgid "I cannot encode ~s as a DPoP proof (because ~a)"
msgstr "je n’ai pas pu encoder ~s comme une preuve DPoP (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1082
+#: src/scm/webid-oidc/errors.scm:1124
#, scheme-format
msgid "I could not fetch a RDF graph at ~a (because ~a)"
msgstr "je n’ai pas pu récupérer de graphe RDF à ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1085
+#: src/scm/webid-oidc/errors.scm:1127
#, scheme-format
msgid "~s is not a client manifest (because ~a)"
msgstr "~s n’est pas un manifeste client (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1088
+#: src/scm/webid-oidc/errors.scm:1130
#, scheme-format
msgid "~s does not authorize redirection URI ~a"
msgstr "~s n’autorise pas l’URI de redirection ~a"
-#: src/scm/webid-oidc/errors.scm:1091
+#: src/scm/webid-oidc/errors.scm:1133
msgid "I cannot serve a public manifest"
msgstr "je ne peux pas servir un manifeste public"
-#: src/scm/webid-oidc/errors.scm:1093
+#: src/scm/webid-oidc/errors.scm:1135
#, scheme-format
msgid "~a does not have a client manifest registration triple"
msgstr "~a n’a pas de triplet d’enregistrement de manifeste client"
-#: src/scm/webid-oidc/errors.scm:1096
+#: src/scm/webid-oidc/errors.scm:1138
#, scheme-format
msgid "the client manifest at ~a is advertised for ~a"
msgstr "le manifeste client ~a est publié pour ~a"
-#: src/scm/webid-oidc/errors.scm:1099
+#: src/scm/webid-oidc/errors.scm:1141
#, scheme-format
msgid "I could not fetch the client manifest of ~a (because ~a)"
msgstr "je n’ai pas pu récupérer le manifeste client de ~a (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1102
+#: src/scm/webid-oidc/errors.scm:1144
#, scheme-format
msgid "~s is not an authorization code (because ~a)"
msgstr "~s n’est pas un code d’autorisation (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1105
+#: src/scm/webid-oidc/errors.scm:1147
#, scheme-format
msgid "~s is not an authorization code header (because ~a)"
msgstr "~s n’est pas un en-tête de code d’autorisation (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1108
+#: src/scm/webid-oidc/errors.scm:1150
#, scheme-format
msgid "~s is not an authorization code payload (because ~a)"
msgstr "~s n’est pas un contenu de code d’autorisation (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1111
+#: src/scm/webid-oidc/errors.scm:1153
#, scheme-format
msgid "the current time is ~a, and the authorization code expired at ~a"
msgstr ""
"la date est actuellement ~a, et le code d’autorisation a expiré à la date ~a"
-#: src/scm/webid-oidc/errors.scm:1115
+#: src/scm/webid-oidc/errors.scm:1157
#, scheme-format
msgid "I cannot decode ~s as an authorization code (because ~a)"
msgstr "je n’ai pas pu décoder ~s comme un code d’autorisation (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1118
+#: src/scm/webid-oidc/errors.scm:1160
#, scheme-format
msgid "I cannot encode ~s as an authorization code (because ~a)"
msgstr "je n’ai pas pu encoder ~s comme un code d’autorisation (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1121
+#: src/scm/webid-oidc/errors.scm:1163
#, scheme-format
msgid "there is no such refresh token as ~s"
msgstr "il n’y a pas de jeton de rafraîchissement ~s"
-#: src/scm/webid-oidc/errors.scm:1124
+#: src/scm/webid-oidc/errors.scm:1166
#, scheme-format
msgid ""
"the refresh token is bound to a key confirmed as ~s, but it is used with key "
@@ -587,45 +587,45 @@ msgstr ""
"Le jeton de rafraîchissement est lié à une clé confirmée par ~s, mais il est "
"utilisé avec la clé ~s"
-#: src/scm/webid-oidc/errors.scm:1127
+#: src/scm/webid-oidc/errors.scm:1169
#, scheme-format
msgid "I cannot decode ~s as an ID token (because ~a)"
msgstr "je n’ai pas pu décoder ~s comme jeton d’identité (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1130
+#: src/scm/webid-oidc/errors.scm:1172
#, scheme-format
msgid "I cannot encode ~s as an ID token (because ~a)"
msgstr "je n’ai pas pu encoder ~s comme un jeton d’identité (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1133
+#: src/scm/webid-oidc/errors.scm:1175
#, scheme-format
msgid "the grant type ~s is not supported"
msgstr "le type d’octroi ~s n’est pas supporté "
-#: src/scm/webid-oidc/errors.scm:1136
+#: src/scm/webid-oidc/errors.scm:1178
msgid "there is no authorization code in the request"
msgstr "il n’y a pas de code d’autorisation dans la requête"
-#: src/scm/webid-oidc/errors.scm:1138
+#: src/scm/webid-oidc/errors.scm:1180
msgid "there is no refresh token in the request"
msgstr "il n’y a pas de jeton de rafraîchissement dans la requête"
-#: src/scm/webid-oidc/errors.scm:1140
+#: src/scm/webid-oidc/errors.scm:1182
#, scheme-format
msgid "~s is not an ID token (because ~a)"
msgstr "~s n’est pas un jeton d’identité (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1143
+#: src/scm/webid-oidc/errors.scm:1185
#, scheme-format
msgid "~s is not an ID token header (because ~a)"
msgstr "~s n’est pas un en-tête de jeton d’identité (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1146
+#: src/scm/webid-oidc/errors.scm:1188
#, scheme-format
msgid "~s is not an ID token payload (because ~a)"
msgstr "~s n’est pas un contenu de jeton d’identité (parce que ~a)"
-#: src/scm/webid-oidc/errors.scm:1149
+#: src/scm/webid-oidc/errors.scm:1191
#, scheme-format
msgid ""
"I couldn’t set the locale to ~s as an approximation of the client locale ~s"
@@ -633,71 +633,105 @@ msgstr ""
"je n’ai pas pu définir la locale à ~s comme approximation de la locale du "
"client ~s"
-#: src/scm/webid-oidc/errors.scm:1152
+#: src/scm/webid-oidc/errors.scm:1194
#, scheme-format
msgid "~s does not admit ~s as an identity provider"
msgstr "~s n’admet pas ~s comme fournisseur d’identité"
-#: src/scm/webid-oidc/errors.scm:1157
+#: src/scm/webid-oidc/errors.scm:1197
+#, scheme-format
+msgid ""
+"~a is neither an identity provider (because ~a) nor a webid (because ~a)"
+msgstr ""
+"~a n’est ni un fournisseur d’identité (parce que ~a) ni un webid (parce que "
+"~a)"
+
+#: src/scm/webid-oidc/errors.scm:1202
+#, scheme-format
+msgid "the token request failed (because ~a)"
+msgstr "la requête de jeton a échoué (parce que ~a)"
+
+#: src/scm/webid-oidc/errors.scm:1205
+#, scheme-format
+msgid "you don’t have a refresh token for identity ~a certified by ~a in ~s"
+msgstr ""
+"vous n’avez pas de jeton de rafraîchissement pour l’identité ~a certifié par "
+"~a dans ~s"
+
+#: src/scm/webid-oidc/errors.scm:1210
+#, scheme-format
+msgid "all identity provider candidates for ~a failed: ~a"
+msgstr "tous les candidats de fournisseurs d’identité pour ~a ont échoué : ~a"
+
+#: src/scm/webid-oidc/errors.scm:1214
+#, scheme-format
+msgid "~s failed (because ~a)"
+msgstr "~s a échoué (parce que ~a)"
+
+#: src/scm/webid-oidc/errors.scm:1217
+msgid ", "
+msgstr ", "
+
+#: src/scm/webid-oidc/errors.scm:1221
msgid "that’s it"
msgstr "c’est tout"
-#: src/scm/webid-oidc/errors.scm:1161
+#: src/scm/webid-oidc/errors.scm:1225
#, scheme-format
msgid "~a and ~a"
msgstr "~a et ~a"
-#: src/scm/webid-oidc/errors.scm:1164
+#: src/scm/webid-oidc/errors.scm:1228
#, scheme-format
msgid "~a, ~a"
msgstr "~a, ~a"
-#: src/scm/webid-oidc/errors.scm:1168
+#: src/scm/webid-oidc/errors.scm:1232
#, scheme-format
msgid "the signature ~a does not match key ~s with payload ~a"
msgstr "la signature ~a ne correspond pas à la clé ~s avec le contenu ~a"
-#: src/scm/webid-oidc/errors.scm:1171
+#: src/scm/webid-oidc/errors.scm:1235
msgid "there is an undefined variable"
msgstr "il y a une variable non définie"
-#: src/scm/webid-oidc/errors.scm:1173
+#: src/scm/webid-oidc/errors.scm:1237
#, scheme-format
msgid "the origin is ~a"
msgstr "l’origine est ~a"
-#: src/scm/webid-oidc/errors.scm:1176
+#: src/scm/webid-oidc/errors.scm:1240
#, scheme-format
msgid "a message is attached: ~a"
msgstr "un message est attaché : ~a"
-#: src/scm/webid-oidc/errors.scm:1179
+#: src/scm/webid-oidc/errors.scm:1243
#, scheme-format
msgid "the values ~s are problematic"
msgstr "les valeurs ~s sont problématiques"
-#: src/scm/webid-oidc/errors.scm:1182
+#: src/scm/webid-oidc/errors.scm:1246
msgid "there is a kind and args"
msgstr "il y a un type et des arguments"
-#: src/scm/webid-oidc/errors.scm:1184
+#: src/scm/webid-oidc/errors.scm:1248
msgid "there is an assertion failure"
msgstr "il y a un échec d’assertion"
-#: src/scm/webid-oidc/errors.scm:1186
+#: src/scm/webid-oidc/errors.scm:1250
#, scheme-format
msgid "the program quits with code ~a"
msgstr "le programme quitte avec le code ~a"
-#: src/scm/webid-oidc/errors.scm:1189
+#: src/scm/webid-oidc/errors.scm:1253
msgid "the program cannot recover from this exception"
msgstr "le programme ne peut pas récupérer après cette exception"
-#: src/scm/webid-oidc/errors.scm:1191
+#: src/scm/webid-oidc/errors.scm:1255
msgid "there is an error"
msgstr "il y a une erreur"
-#: src/scm/webid-oidc/errors.scm:1193
+#: src/scm/webid-oidc/errors.scm:1257
#, scheme-format
msgid "Unhandled exception type ~a."
msgstr "Type d’exception non pris en charge ~a."
@@ -1274,35 +1308,6 @@ msgstr ""
" définit le port à lier.\n"
#, scheme-format
-#~ msgid ""
-#~ "~a is neither an identity provider (because ~a) nor a webid (because ~a)"
-#~ msgstr ""
-#~ "~a n’est ni un fournisseur d’identité (parce que ~a) ni un webid (parce "
-#~ "que ~a)"
-
-#, scheme-format
-#~ msgid "the token request failed (because ~a)"
-#~ msgstr "la requête de jeton a échoué (parce que ~a)"
-
-#, scheme-format
-#~ msgid "you don’t have a refresh token for identity ~a certified by ~a in ~s"
-#~ msgstr ""
-#~ "vous n’avez pas de jeton de rafraîchissement pour l’identité ~a certifié "
-#~ "par ~a dans ~s"
-
-#, scheme-format
-#~ msgid "all identity provider candidates for ~a failed: ~a"
-#~ msgstr ""
-#~ "tous les candidats de fournisseurs d’identité pour ~a ont échoué : ~a"
-
-#, scheme-format
-#~ msgid "~s failed (because ~a)"
-#~ msgstr "~s a échoué (parce que ~a)"
-
-#~ msgid ", "
-#~ msgstr ", "
-
-#, scheme-format
#~ msgid "the resource ~s could not be found (because ~a)"
#~ msgstr "la ressource ~s n’a pas été trouvée (parce que ~a)"
diff --git a/po/webid-oidc.pot b/po/webid-oidc.pot
index f3e5c51..2f7d082 100644
--- a/po/webid-oidc.pot
+++ b/po/webid-oidc.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: webid-oidc SNAPSHOT\n"
"Report-Msgid-Bugs-To: vivien@planete-kraus.eu\n"
-"POT-Creation-Date: 2021-06-05 16:20+0200\n"
+"POT-Creation-Date: 2021-06-05 16:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -122,565 +122,595 @@ msgstr ""
msgid "Usage: generate-key [NUMBER OF BITS | CURVE]\n"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:839
+#: src/scm/webid-oidc/errors.scm:881
msgid "that’s how it is"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:844
+#: src/scm/webid-oidc/errors.scm:886
#, scheme-format
msgid "the value ~s is not a base64 string (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:847
+#: src/scm/webid-oidc/errors.scm:889
#, scheme-format
msgid "the value ~s is not JSON (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:850
+#: src/scm/webid-oidc/errors.scm:892
#, scheme-format
msgid "the value ~s is not Turtle (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:853
+#: src/scm/webid-oidc/errors.scm:895
#, scheme-format
msgid "the value ~s does not identify an elleptic curve"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:858
+#: src/scm/webid-oidc/errors.scm:900
#, scheme-format
msgid "the value ~s does not identify a JWK (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:860
+#: src/scm/webid-oidc/errors.scm:902
#, scheme-format
msgid "the value ~s does not identify a JWK"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:865
+#: src/scm/webid-oidc/errors.scm:907
#, scheme-format
msgid "the value ~s does not identify a public JWK (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:867
+#: src/scm/webid-oidc/errors.scm:909
#, scheme-format
msgid "the value ~s does not identify a public JWK"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:872
+#: src/scm/webid-oidc/errors.scm:914
#, scheme-format
msgid "the value ~s does not identify a private JWK (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:874
+#: src/scm/webid-oidc/errors.scm:916
#, scheme-format
msgid "the value ~s does not identify a private JWK"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:879
+#: src/scm/webid-oidc/errors.scm:921
#, scheme-format
msgid "the value ~s does not identify a JWKS (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:881
+#: src/scm/webid-oidc/errors.scm:923
#, scheme-format
msgid "the value ~s does not identify a JWKS"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:884
+#: src/scm/webid-oidc/errors.scm:926
#, scheme-format
msgid "the value ~s does not identify a hash algorithm"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:887
+#: src/scm/webid-oidc/errors.scm:929
#, scheme-format
msgid "the value ~s is not an alist or misses key ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:890
+#: src/scm/webid-oidc/errors.scm:932
#, scheme-format
msgid "the value ~s is not a JWS header (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:893
+#: src/scm/webid-oidc/errors.scm:935
#, scheme-format
msgid "the value ~s is not a JWS payload (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:896
+#: src/scm/webid-oidc/errors.scm:938
#, scheme-format
msgid "the value ~s is not a JWS (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:899
+#: src/scm/webid-oidc/errors.scm:941
#, scheme-format
msgid "the string ~s cannot be split in 3 parts with ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:902
+#: src/scm/webid-oidc/errors.scm:944
#, scheme-format
msgid ""
"all key candidates failed to verify signature ~s with algorithm ~s and "
"payload ~a (there were ~a: ~s)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:905
+#: src/scm/webid-oidc/errors.scm:947
#, scheme-format
msgid "I cannot decode JWS ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:908
+#: src/scm/webid-oidc/errors.scm:950
#, scheme-format
msgid "I cannot encode JWS ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:911
+#: src/scm/webid-oidc/errors.scm:953
#, scheme-format
msgid ""
"the server request unexpectedly failed with code ~a and reason phrase ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:916
+#: src/scm/webid-oidc/errors.scm:958
#, scheme-format
msgid "the header ~a should not have the value ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:918
+#: src/scm/webid-oidc/errors.scm:960
#, scheme-format
msgid "the header ~a should be present"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:921
+#: src/scm/webid-oidc/errors.scm:963
#, scheme-format
msgid "the server response wasn't expected: ~s (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:927
+#: src/scm/webid-oidc/errors.scm:969
#, scheme-format
msgid "the value ~s is not an OIDC configuration (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:932
+#: src/scm/webid-oidc/errors.scm:974
#, scheme-format
msgid "the webid field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:933
+#: src/scm/webid-oidc/errors.scm:975
msgid "the webid field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:937
+#: src/scm/webid-oidc/errors.scm:979
#, scheme-format
msgid "the sub field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:938
+#: src/scm/webid-oidc/errors.scm:980
msgid "the sub field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:942
+#: src/scm/webid-oidc/errors.scm:984
#, scheme-format
msgid "the iss field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:943
+#: src/scm/webid-oidc/errors.scm:985
msgid "the iss field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:947
+#: src/scm/webid-oidc/errors.scm:989
#, scheme-format
msgid "the aud field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:948
+#: src/scm/webid-oidc/errors.scm:990
msgid "the aud field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:952
+#: src/scm/webid-oidc/errors.scm:994
#, scheme-format
msgid "the iat field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:953
+#: src/scm/webid-oidc/errors.scm:995
msgid "the iat field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:957
+#: src/scm/webid-oidc/errors.scm:999
#, scheme-format
msgid "the exp field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:958
+#: src/scm/webid-oidc/errors.scm:1000
msgid "the exp field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:962
+#: src/scm/webid-oidc/errors.scm:1004
#, scheme-format
msgid "the cnf/jkt field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:963
+#: src/scm/webid-oidc/errors.scm:1005
msgid "the cnf/jkt field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:967
+#: src/scm/webid-oidc/errors.scm:1009
#, scheme-format
msgid "the client-id field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:968
+#: src/scm/webid-oidc/errors.scm:1010
msgid "the client-id field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:972
+#: src/scm/webid-oidc/errors.scm:1014
#: src/scm/webid-oidc/authorization-page-unsafe.scm:133
#, scheme-format
msgid "the redirect_uris field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:973
+#: src/scm/webid-oidc/errors.scm:1015
#: src/scm/webid-oidc/authorization-page-unsafe.scm:134
msgid "the redirect_uris field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:977
+#: src/scm/webid-oidc/errors.scm:1019
#, scheme-format
msgid "the typ field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:978
+#: src/scm/webid-oidc/errors.scm:1020
msgid "the typ field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:982
+#: src/scm/webid-oidc/errors.scm:1024
#, scheme-format
msgid "the jwk field is incorrect: ~s (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:984
+#: src/scm/webid-oidc/errors.scm:1026
msgid "the jwk field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:988
+#: src/scm/webid-oidc/errors.scm:1030
#, scheme-format
msgid "the jti field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:989
+#: src/scm/webid-oidc/errors.scm:1031
msgid "the jti field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:993
+#: src/scm/webid-oidc/errors.scm:1035
#, scheme-format
msgid "the nonce field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:994
+#: src/scm/webid-oidc/errors.scm:1036
msgid "the nonce field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:998
+#: src/scm/webid-oidc/errors.scm:1040
#, scheme-format
msgid "the htm field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:999
+#: src/scm/webid-oidc/errors.scm:1041
msgid "the htm field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1003
+#: src/scm/webid-oidc/errors.scm:1045
#, scheme-format
msgid "the htu field is incorrect: ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1004
+#: src/scm/webid-oidc/errors.scm:1046
msgid "the htu field is missing"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1006
+#: src/scm/webid-oidc/errors.scm:1048
#, scheme-format
msgid "~s is not an access token (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1009
+#: src/scm/webid-oidc/errors.scm:1051
#, scheme-format
msgid "~s is not an access token header (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1012
+#: src/scm/webid-oidc/errors.scm:1054
#, scheme-format
msgid "~s is not an access token payload (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1015
+#: src/scm/webid-oidc/errors.scm:1057
#, scheme-format
msgid "~s is not a DPoP proof (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1018
+#: src/scm/webid-oidc/errors.scm:1060
#, scheme-format
msgid "~s is not a DPoP proof header (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1021
+#: src/scm/webid-oidc/errors.scm:1063
#, scheme-format
msgid "~s is not a DPoP proof payload (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1024
+#: src/scm/webid-oidc/errors.scm:1066
#, scheme-format
msgid "I cannot fetch the issuer configuration of ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1031
+#: src/scm/webid-oidc/errors.scm:1073
#, scheme-format
msgid "I cannot fetch the JWKS of ~a at ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1042
+#: src/scm/webid-oidc/errors.scm:1084
#, scheme-format
msgid "the HTTP method is signed for ~s, but ~s was requested"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1045
+#: src/scm/webid-oidc/errors.scm:1087
#, scheme-format
msgid "the HTTP uri is signed for ~a, but ~a was requested"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1048
+#: src/scm/webid-oidc/errors.scm:1090
#, scheme-format
msgid "the date is ~a, but the DPoP proof is signed in the future at ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1052
+#: src/scm/webid-oidc/errors.scm:1094
#, scheme-format
msgid "the date is ~a, but the DPoP proof was signed too long ago at ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1061
+#: src/scm/webid-oidc/errors.scm:1103
#, scheme-format
msgid "the key ~s does not hash to ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1063
+#: src/scm/webid-oidc/errors.scm:1105
#, scheme-format
msgid "the key confirmation of ~s failed (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1065
+#: src/scm/webid-oidc/errors.scm:1107
#, scheme-format
msgid "the key confirmation of ~s failed"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1067
+#: src/scm/webid-oidc/errors.scm:1109
#, scheme-format
msgid "the jti ~s has already been found (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1070
+#: src/scm/webid-oidc/errors.scm:1112
#, scheme-format
msgid "I cannot decode ~s as an access token (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1073
+#: src/scm/webid-oidc/errors.scm:1115
#, scheme-format
msgid "I cannot encode ~s as an access token with key ~s (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1076
+#: src/scm/webid-oidc/errors.scm:1118
#, scheme-format
msgid "I cannot decode ~s as a DPoP proof (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1079
+#: src/scm/webid-oidc/errors.scm:1121
#, scheme-format
msgid "I cannot encode ~s as a DPoP proof (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1082
+#: src/scm/webid-oidc/errors.scm:1124
#, scheme-format
msgid "I could not fetch a RDF graph at ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1085
+#: src/scm/webid-oidc/errors.scm:1127
#, scheme-format
msgid "~s is not a client manifest (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1088
+#: src/scm/webid-oidc/errors.scm:1130
#, scheme-format
msgid "~s does not authorize redirection URI ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1091
+#: src/scm/webid-oidc/errors.scm:1133
msgid "I cannot serve a public manifest"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1093
+#: src/scm/webid-oidc/errors.scm:1135
#, scheme-format
msgid "~a does not have a client manifest registration triple"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1096
+#: src/scm/webid-oidc/errors.scm:1138
#, scheme-format
msgid "the client manifest at ~a is advertised for ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1099
+#: src/scm/webid-oidc/errors.scm:1141
#, scheme-format
msgid "I could not fetch the client manifest of ~a (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1102
+#: src/scm/webid-oidc/errors.scm:1144
#, scheme-format
msgid "~s is not an authorization code (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1105
+#: src/scm/webid-oidc/errors.scm:1147
#, scheme-format
msgid "~s is not an authorization code header (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1108
+#: src/scm/webid-oidc/errors.scm:1150
#, scheme-format
msgid "~s is not an authorization code payload (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1111
+#: src/scm/webid-oidc/errors.scm:1153
#, scheme-format
msgid "the current time is ~a, and the authorization code expired at ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1115
+#: src/scm/webid-oidc/errors.scm:1157
#, scheme-format
msgid "I cannot decode ~s as an authorization code (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1118
+#: src/scm/webid-oidc/errors.scm:1160
#, scheme-format
msgid "I cannot encode ~s as an authorization code (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1121
+#: src/scm/webid-oidc/errors.scm:1163
#, scheme-format
msgid "there is no such refresh token as ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1124
+#: src/scm/webid-oidc/errors.scm:1166
#, scheme-format
msgid ""
"the refresh token is bound to a key confirmed as ~s, but it is used with key "
"~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1127
+#: src/scm/webid-oidc/errors.scm:1169
#, scheme-format
msgid "I cannot decode ~s as an ID token (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1130
+#: src/scm/webid-oidc/errors.scm:1172
#, scheme-format
msgid "I cannot encode ~s as an ID token (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1133
+#: src/scm/webid-oidc/errors.scm:1175
#, scheme-format
msgid "the grant type ~s is not supported"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1136
+#: src/scm/webid-oidc/errors.scm:1178
msgid "there is no authorization code in the request"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1138
+#: src/scm/webid-oidc/errors.scm:1180
msgid "there is no refresh token in the request"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1140
+#: src/scm/webid-oidc/errors.scm:1182
#, scheme-format
msgid "~s is not an ID token (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1143
+#: src/scm/webid-oidc/errors.scm:1185
#, scheme-format
msgid "~s is not an ID token header (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1146
+#: src/scm/webid-oidc/errors.scm:1188
#, scheme-format
msgid "~s is not an ID token payload (because ~a)"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1149
+#: src/scm/webid-oidc/errors.scm:1191
#, scheme-format
msgid ""
"I couldn’t set the locale to ~s as an approximation of the client locale ~s"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1152
+#: src/scm/webid-oidc/errors.scm:1194
#, scheme-format
msgid "~s does not admit ~s as an identity provider"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1157
+#: src/scm/webid-oidc/errors.scm:1197
+#, scheme-format
+msgid ""
+"~a is neither an identity provider (because ~a) nor a webid (because ~a)"
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1202
+#, scheme-format
+msgid "the token request failed (because ~a)"
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1205
+#, scheme-format
+msgid "you don’t have a refresh token for identity ~a certified by ~a in ~s"
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1210
+#, scheme-format
+msgid "all identity provider candidates for ~a failed: ~a"
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1214
+#, scheme-format
+msgid "~s failed (because ~a)"
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1217
+msgid ", "
+msgstr ""
+
+#: src/scm/webid-oidc/errors.scm:1221
msgid "that’s it"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1161
+#: src/scm/webid-oidc/errors.scm:1225
#, scheme-format
msgid "~a and ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1164
+#: src/scm/webid-oidc/errors.scm:1228
#, scheme-format
msgid "~a, ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1168
+#: src/scm/webid-oidc/errors.scm:1232
#, scheme-format
msgid "the signature ~a does not match key ~s with payload ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1171
+#: src/scm/webid-oidc/errors.scm:1235
msgid "there is an undefined variable"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1173
+#: src/scm/webid-oidc/errors.scm:1237
#, scheme-format
msgid "the origin is ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1176
+#: src/scm/webid-oidc/errors.scm:1240
#, scheme-format
msgid "a message is attached: ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1179
+#: src/scm/webid-oidc/errors.scm:1243
#, scheme-format
msgid "the values ~s are problematic"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1182
+#: src/scm/webid-oidc/errors.scm:1246
msgid "there is a kind and args"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1184
+#: src/scm/webid-oidc/errors.scm:1248
msgid "there is an assertion failure"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1186
+#: src/scm/webid-oidc/errors.scm:1250
#, scheme-format
msgid "the program quits with code ~a"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1189
+#: src/scm/webid-oidc/errors.scm:1253
msgid "the program cannot recover from this exception"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1191
+#: src/scm/webid-oidc/errors.scm:1255
msgid "there is an error"
msgstr ""
-#: src/scm/webid-oidc/errors.scm:1193
+#: src/scm/webid-oidc/errors.scm:1257
#, scheme-format
msgid "Unhandled exception type ~a."
msgstr ""
diff --git a/src/scm/webid-oidc/Makefile.am b/src/scm/webid-oidc/Makefile.am
index 8f5f105..1f9cb5d 100644
--- a/src/scm/webid-oidc/Makefile.am
+++ b/src/scm/webid-oidc/Makefile.am
@@ -22,7 +22,8 @@ dist_webidoidcmod_DATA += \
%reldir%/provider-confirmation.scm \
%reldir%/resource-server.scm \
%reldir%/hello-world.scm \
- %reldir%/reverse-proxy.scm
+ %reldir%/reverse-proxy.scm \
+ %reldir%/client.scm
webidoidcgo_DATA += \
%reldir%/errors.go \
@@ -48,6 +49,7 @@ webidoidcgo_DATA += \
%reldir%/provider-confirmation.go \
%reldir%/resource-server.go \
%reldir%/hello-world.go \
- %reldir%/reverse-proxy.go
+ %reldir%/reverse-proxy.go \
+ %reldir%/client.go
EXTRA_DIST += %reldir%/ChangeLog
diff --git a/src/scm/webid-oidc/cache.scm b/src/scm/webid-oidc/cache.scm
index 54f2183..961b73c 100644
--- a/src/scm/webid-oidc/cache.scm
+++ b/src/scm/webid-oidc/cache.scm
@@ -35,7 +35,7 @@
(define (default-cache-dir)
(let ((xdg-cache-home
(or (getenv "XDG_CACHE_HOME")
- (format #f "~a/.cache"))))
+ (format #f "~a/.cache" (getenv "HOME")))))
(format #f "~a/webid-oidc" xdg-cache-home)))
(define (event? percents)
diff --git a/src/scm/webid-oidc/client.scm b/src/scm/webid-oidc/client.scm
new file mode 100644
index 0000000..1cb81ed
--- /dev/null
+++ b/src/scm/webid-oidc/client.scm
@@ -0,0 +1,487 @@
+(define-module (webid-oidc client)
+ #:use-module (webid-oidc errors)
+ #:use-module (webid-oidc provider-confirmation)
+ #:use-module (webid-oidc oidc-configuration)
+ #:use-module (webid-oidc oidc-id-token)
+ #:use-module (webid-oidc dpop-proof)
+ #:use-module (webid-oidc jwk)
+ #:use-module ((webid-oidc stubs) #:prefix stubs:)
+ #:use-module ((webid-oidc refresh-token) #:prefix refresh:)
+ #:use-module (web uri)
+ #:use-module (web client)
+ #:use-module (web response)
+ #:use-module (web server)
+ #:use-module (web http)
+ #:use-module (ice-9 optargs)
+ #:use-module (ice-9 receive)
+ #:use-module (srfi srfi-19)
+ #:use-module (rnrs bytevectors))
+
+(define*-public (authorize host-or-webid
+ #:key
+ (client-id #f)
+ (redirect-uri #f)
+ (state #f)
+ (http-get http-get))
+ (define cannot-be-webid #f)
+ (define candidate-errors '())
+ ;; host-or-webid can be: the host (as a string), an URI (as a string
+ ;; or an URI). 3 differents things.
+ (when (string? host-or-webid)
+ ;; If it’s a string, it can be either a host name or a URI.
+ (set! host-or-webid
+ (catch #t
+ (lambda ()
+ (let ((urified (string->uri host-or-webid)))
+ (if urified
+ urified
+ (error "It’s not a string representing an URI."))))
+ (lambda error
+ (build-uri 'https #:host host-or-webid)))))
+ ;; client-id and redirect-uri are required, state must be a string.
+ (when (string? client-id)
+ (set! client-id (string->uri client-id)))
+ (when (string? redirect-uri)
+ (set! redirect-uri (string->uri redirect-uri)))
+ (let ((host-candidates
+ (with-exception-handler
+ (lambda (why-not-webid)
+ ;; try as an identity provider
+ (set! cannot-be-webid why-not-webid)
+ (build-uri 'https
+ #:userinfo (uri-userinfo host-or-webid)
+ #:host (uri-host host-or-webid)
+ #:port (uri-port host-or-webid)))
+ (lambda ()
+ (get-provider-confirmations host-or-webid #:http-get http-get))
+ #:unwind? #t)))
+ (let ((configurations
+ (if cannot-be-webid
+ (with-exception-handler
+ (lambda (why-not-identity-provider)
+ (raise-neither-identity-provider-nor-webid
+ host-or-webid
+ why-not-identity-provider
+ cannot-be-webid))
+ (lambda ()
+ (cons (uri->string host-candidates)
+ (get-oidc-configuration (uri-host host-candidates)
+ #:userinfo (uri-userinfo host-candidates)
+ #:port (uri-port host-candidates)
+ #:http-get http-get))))
+ (filter
+ (lambda (cfg) cfg)
+ (map
+ (lambda (host)
+ (with-exception-handler
+ (lambda (cause)
+ (set! candidate-errors (acons host cause candidate-errors))
+ #f)
+ (lambda ()
+ (cons (uri->string host)
+ (get-oidc-configuration (uri-host host)
+ #:userinfo (uri-userinfo host)
+ #:port (uri-port host)
+ #:http-get http-get)))
+ #:unwind? #t))
+ host-candidates)))))
+ (let ((authorization-endpoints
+ (if cannot-be-webid
+ (with-exception-handler
+ (lambda (why-not-identity-provider)
+ (raise-neither-identity-provider-nor-webid
+ host-or-webid
+ why-not-identity-provider
+ cannot-be-webid))
+ (lambda ()
+ (let ((host (car configurations))
+ (cfg (cdr configurations)))
+ (cons host (oidc-configuration-authorization-endpoint cfg)))))
+ (map
+ (lambda (host/cfg)
+ (let ((host (car host/cfg))
+ (cfg (cdr host/cfg)))
+ (with-exception-handler
+ (lambda (cause)
+ (set! candidate-errors (acons (string->uri host) cause
+ candidate-errors)))
+ (lambda ()
+ (cons host
+ (oidc-configuration-authorization-endpoint cfg)))
+ #:unwind? #t)))
+ configurations))))
+ (if cannot-be-webid
+ (let ((host (car authorization-endpoints))
+ (authz (cdr authorization-endpoints)))
+ (list
+ (cons
+ host
+ (build-uri (uri-scheme authz)
+ #:userinfo (uri-userinfo authz)
+ #:host (uri-host authz)
+ #:port (uri-port authz)
+ #:path (uri-path authz)
+ #:query (format #f "client_id=~a&redirect_uri=~a~a"
+ (uri-encode (uri->string client-id))
+ (uri-encode (uri->string redirect-uri))
+ (if state
+ (format #f "&state=~a"
+ (uri-encode state))
+ ""))))))
+ (let ((final-candidates
+ (map
+ (lambda (host/authorization-endpoint)
+ (let ((host (car host/authorization-endpoint))
+ (authorization-endpoint (cdr host/authorization-endpoint)))
+ (cons
+ host
+ (build-uri (uri-scheme authorization-endpoint)
+ #:userinfo (uri-userinfo authorization-endpoint)
+ #:host (uri-host authorization-endpoint)
+ #:port (uri-port authorization-endpoint)
+ #:path (uri-path authorization-endpoint)
+ #:query (format #f "client_id=~a&redirect_uri=~a~a"
+ (uri-encode (uri->string client-id))
+ (uri-encode (uri->string redirect-uri))
+ (if state
+ (format #f "&state=~a"
+ (uri-encode state))
+ ""))))))
+ authorization-endpoints)))
+ (when (null? final-candidates)
+ (raise-no-provider-candidates host-or-webid candidate-errors))
+ final-candidates))))))
+
+(define the-current-time current-time)
+
+(define*-public (token host client-key
+ #:key
+ (authorization-code #f)
+ (refresh-token #f)
+ (http-get http-get)
+ (http-post http-post)
+ (current-time #f))
+ (unless (or authorization-code refresh-token)
+ (scm-error 'wrong-type-arg "token"
+ "You need to either set #:authorization-code or #:refresh-token."
+ '()
+ (list authorization-code)))
+ (unless current-time
+ (set! current-time the-current-time))
+ (when (thunk? current-time)
+ (set! current-time (current-time)))
+ (when (integer? current-time)
+ (set! current-time (make-time time-utc 0 current-time)))
+ (when (time? current-time)
+ (set! current-time (time-utc->date current-time)))
+ (let ((token-endpoint
+ (oidc-configuration-token-endpoint
+ (get-oidc-configuration host #:http-get http-get)))
+ (grant-type
+ (if authorization-code
+ "authorization_code"
+ "refresh_token")))
+ (let ((dpop-proof
+ (issue-dpop-proof
+ client-key
+ #:alg (case (kty client-key)
+ ((EC) 'ES256)
+ ((RSA) 'RS256)
+ (else
+ (error "Unknown key type of ~S." client-key)))
+ #:htm 'POST
+ #:htu token-endpoint
+ #:iat current-time)))
+ (receive (response response-body)
+ (http-post token-endpoint
+ #:body
+ (string-join
+ (map
+ (lambda (arg)
+ (string-append (uri-encode (car arg))
+ "="
+ (uri-encode (cdr arg))))
+ `(("grant_type" . ,grant-type)
+ ,@(if authorization-code
+ `(("code" . ,authorization-code))
+ '())
+ ,@(if refresh-token
+ `(("refresh_token" . ,refresh-token))
+ '())))
+ "&")
+ #:headers
+ `((content-type application/x-www-form-urlencoded)
+ (dpop . ,dpop-proof)))
+ (with-exception-handler
+ (lambda (error)
+ (raise-token-request-failed error))
+ (lambda ()
+ (when (bytevector? response-body)
+ (set! response-body (utf8->string response-body)))
+ (with-exception-handler
+ (lambda (error)
+ (raise-unexpected-response response error))
+ (lambda ()
+ (unless (eqv? (response-code response) 200)
+ (raise-request-failed-unexpectedly
+ (response-code response)
+ (response-reason-phrase response)))
+ (unless (and (response-content-type response)
+ (eq? (car (response-content-type response 'application/json))))
+ (raise-unexpected-header-value 'content-type (response-content-type response)))
+ (stubs:json-string->scm response-body)))))))))
+
+(define (default-dir)
+ (let ((xdg-data-home
+ (or
+ (getenv "XDG_DATA_HOME")
+ (format #f "~a/.local/share"
+ (getenv "HOME")))))
+ (format #f "~a/webid-oidc" xdg-data-home)))
+
+(define*-public (list-profiles #:key (dir default-dir))
+ (when (thunk? dir)
+ (set! dir (dir)))
+ (map (lambda (profile)
+ (list
+ (string->uri (car profile)) ;; webid
+ (string->uri (cadr profile)) ;; issuer
+ (caddr profile) ;; refresh token
+ (cadddr profile))) ;; key
+ (catch #t
+ (lambda ()
+ (call-with-input-file (string-append dir "/profiles")
+ read))
+ (lambda error
+ (format (current-error-port) "Could not read profiles: ~s\n" error)
+ '()))))
+
+(define* (add-profile webid issuer refresh-token key
+ #:key (dir default-dir))
+ (when (thunk? dir)
+ (set! dir (dir)))
+ (let ((other-profiles (list-profiles #:dir dir)))
+ (stubs:atomically-update-file
+ (string-append dir "/profiles")
+ (lambda (port)
+ (write
+ (map (lambda (profile)
+ (list
+ (uri->string (car profile)) ;; webid
+ (uri->string (cadr profile)) ;; issuer
+ (caddr profile) ;; refresh token
+ key)) ;; key
+ (cons `(,webid
+ ,issuer
+ ,refresh-token)
+ other-profiles))
+ port)))))
+
+(define*-public (setup get-host/webid choose-provider browse-authorization-uri
+ #:key
+ (client-id #f)
+ (redirect-uri #f)
+ (dir default-dir)
+ (http-get http-get)
+ (http-post http-post)
+ (current-time #f))
+ (when (thunk? dir)
+ (set! dir (dir)))
+ (let ((host/webid (get-host/webid)))
+ (let ((authorization-uris
+ (authorize host/webid
+ #:client-id client-id
+ #:redirect-uri redirect-uri
+ #:http-get http-get))
+ (key (generate-key #:n-size 2048)))
+ (let ((provider (choose-provider (map car authorization-uris))))
+ (let ((authz-uri (assq-ref authorization-uris provider)))
+ (let ((authz-code (browse-authorization-uri authz-uri)))
+ (let ((params
+ (token host/webid key
+ #:authorization-code authz-code
+ #:http-get http-get
+ #:http-post http-post
+ #:current-time current-time)))
+ (let ((id-token (id-token-decode (assq-ref params 'id_token)
+ #:http-get http-get))
+ (access-token (assq-ref params 'access_token))
+ (refresh-token (assq-ref params 'refresh_token)))
+ (when refresh-token
+ ;; Save it to disk
+ (add-profile (id-token-webid id-token)
+ (id-token-iss id-token)
+ refresh-token
+ key
+ #:dir dir))
+ (values (cdr id-token) access-token key)))))))))
+
+(define*-public (login webid issuer refresh-token key
+ #:key
+ (dir default-dir)
+ (http-get http-get)
+ (http-post http-post)
+ (current-time #f))
+ (when (string? webid)
+ (set! webid (string->uri webid)))
+ (when (string? issuer)
+ (set! issuer (string->uri issuer)))
+ (let ((iss-host (uri-host issuer)))
+ (let ((params
+ (token iss-host key
+ #:refresh-token refresh-token
+ #:http-get http-get
+ #:http-post http-post
+ #:current-time current-time)))
+ (let ((id-token (id-token-decode (assq-ref params 'id_token)
+ #:http-get http-get))
+ (access-token (assq-ref params 'access_token))
+ (new-refresh-token (assq-ref params 'refresh-token)))
+ (when (and new-refresh-token
+ (not (equal? refresh-token new-refresh-token)))
+ ;; The refresh token has been updated
+ (add-profile (id-token-webid id-token)
+ (id-token-iss id-token)
+ refresh-token
+ key
+ #:dir dir))
+ (values (cdr id-token) access-token key)))))
+
+(define*-public (refresh id-token
+ key
+ #:key
+ (dir default-dir)
+ (http-get http-get)
+ (http-post http-post)
+ (current-time #f))
+ (when (thunk? dir)
+ (set! dir (dir)))
+ (when (id-token-payload? id-token)
+ ;; For convenience, we’d like a full ID token to use the ID token
+ ;; API.
+ (set! id-token (cons `((alg . "HS256")) id-token)))
+ (let ((profiles (list-profiles #:dir dir)))
+ (letrec ((find-refresh-token
+ (lambda (profiles)
+ (when (null? profiles)
+ (raise-profile-not-found (id-token-webid id-token)
+ (id-token-iss id-token)
+ dir))
+ (let ((prof (car profiles))
+ (others (cdr profiles)))
+ (let ((webid (car prof))
+ (issuer (cadr prof))
+ (refresh (caddr prof)))
+ (if (and (equal? webid (id-token-webid id-token))
+ (equal? issuer (id-token-iss id-token)))
+ refresh
+ (find-refresh-token others)))))))
+ (login (id-token-webid id-token)
+ (id-token-iss id-token)
+ (find-refresh-token (profiles))
+ key
+ #:dir dir
+ #:http-get http-get
+ #:http-post http-post
+ #:current-time current-time))))
+
+(define* (renew-if-expired id-token access-token key
+ date
+ #:key
+ (dir default-dir)
+ (http-get http-get)
+ (http-post http-post))
+ ;; Since we’re not supposed to decode the access token, we’re
+ ;; judging from the ID token to know if it has expired.
+ (when (date? date)
+ (set! date (date->time-utc date)))
+ (when (time? date)
+ (set! date (time-second date)))
+ (when (id-token-payload? id-token)
+ ;; See the refresh function
+ (set! id-token (cons `((alg . "HS256")) id-token)))
+ (let ((exp (id-token-exp id-token)))
+ (set! exp (date->time-utc exp))
+ (set! exp (time-second exp))
+ (if (>= date exp)
+ (refresh id-token key
+ #:dir dir
+ #:http-get http-get
+ #:http-post http-post
+ #:current-time date)
+ (values id-token access-token key))))
+
+(define*-public (make-client id-token access-token key
+ #:key
+ (dir default-dir)
+ (http-get http-get)
+ (http-post http-post)
+ (http-request http-request)
+ (current-time the-current-time))
+ ;; HACK: guile does not support other authentication schemes in
+ ;; WWW-Authenticate than Basic, so it will crash when a response
+ ;; containing that header will be issued.
+ (declare-header! "WWW-Authenticate" string->symbol symbol? write)
+ (define (handler uri method headers other-args current-time retry?)
+ (let ((proof (issue-dpop-proof
+ key
+ #:alg (case (kty key)
+ ((EC) 'ES256)
+ ((RSA) 'RS256)
+ (else
+ (error "Unknown key type of ~S." key)))
+ #:htm method
+ #:htu uri
+ #:iat current-time)))
+ (receive (response response-body)
+ (apply http-request uri
+ #:method method
+ #:headers (append `((dpop . ,proof)
+ (Authorization . ,(string-append "DPoP " access-token)))
+ headers)
+ other-args)
+ (let ((server-date (response-date response))
+ (code (response-code response)))
+ (if (and retry? (eqv? code 401))
+ ;; Maybe the access token has expired?
+ (receive (new-id-token new-access-token new-key)
+ (renew-if-expired id-token access-token key server-date
+ #:dir dir
+ #:http-get http-get
+ #:http-post http-post)
+ (if (equal? access-token new-access-token)
+ ;; No, it’s just that way.
+ (values response response-body)
+ ;; Ah, we have a new access token
+ (begin
+ (set! id-token new-id-token)
+ (set! access-token new-access-token)
+ (set! key new-key)
+ (handler uri method headers other-args current-time #f))))
+ (values response response-body))))))
+ (define (parse-args uri method headers other-args-rev rest)
+ (if (null? rest)
+ (let ((the-current-time current-time))
+ (when (thunk? the-current-time)
+ (set! the-current-time (the-current-time)))
+ (when (integer? the-current-time)
+ (set! the-current-time (make-time time-utc 0 the-current-time)))
+ (when (time? the-current-time)
+ (set! the-current-time (time-utc->date the-current-time)))
+ (handler uri method headers (reverse other-args-rev) the-current-time #t))
+ (let ((kw (car rest)))
+ (case kw
+ ((#:method)
+ (if (null? (cdr rest))
+ (parse-args uri method headers (cons kw other-args-rev) '())
+ (parse-args uri (cadr rest) headers other-args-rev (cddr rest))))
+ ((#:headers)
+ (if (null? (cdr rest))
+ (parse-args uri method headers (cons kw other-args-rev) '())
+ (parse-args uri method (append headers (cadr rest)) other-args-rev (cddr rest))))
+ (else
+ (parse-args uri method headers (cons kw other-args-rev) '()))))))
+ (define (parse-http-request-args uri args)
+ (parse-args uri 'GET '() '() args))
+ (lambda (uri . args)
+ (parse-http-request-args uri args)))
diff --git a/src/scm/webid-oidc/errors.scm b/src/scm/webid-oidc/errors.scm
index 4a62abb..52f5db8 100644
--- a/src/scm/webid-oidc/errors.scm
+++ b/src/scm/webid-oidc/errors.scm
@@ -455,9 +455,10 @@
&external-error
'(issuer cause)))
-(define-public (raise-cannot-fetch-issuer-configuration issuer cause)
+(define*-public (raise-cannot-fetch-issuer-configuration issuer cause #:key (recoverable? #f))
(raise-exception
- ((record-constructor &cannot-fetch-issuer-configuration) issuer cause)))
+ ((record-constructor &cannot-fetch-issuer-configuration) issuer cause)
+ #:continuable? recoverable?))
(define-public &cannot-fetch-jwks
(make-exception-type
@@ -828,6 +829,47 @@
(raise-exception
((record-constructor &unconfirmed-provider) subject provider)))
+(define-public &neither-identity-provider-nor-webid
+ (make-exception-type
+ '&neither-identity-provider-nor-webid
+ &external-error
+ '(uri why-not-identity-provider why-not-webid)))
+
+(define-public (raise-neither-identity-provider-nor-webid uri why-not-identity-provider why-not-webid)
+ (raise-exception
+ ((record-constructor &neither-identity-provider-nor-webid)
+ uri why-not-identity-provider why-not-webid)))
+
+(define-public &token-request-failed
+ (make-exception-type
+ '&token-request-failed
+ &external-error
+ '(cause)))
+
+(define-public (raise-token-request-failed cause)
+ (raise-exception
+ ((record-constructor &token-request-failed) cause)))
+
+(define-public &profile-not-found
+ (make-exception-type
+ '&profile-not-found
+ &external-error
+ '(webid iss dir)))
+
+(define-public (raise-profile-not-found webid iss dir)
+ (raise-exception
+ ((record-constructor &profile-not-found) webid iss dir)))
+
+(define-public &no-provider-candidates
+ (make-exception-type
+ '&no-provider-candidates
+ &external-error
+ '(webid causes)))
+
+(define-public (raise-no-provider-candidates webid causes)
+ (raise-exception
+ ((record-constructor &no-provider-candidates) webid causes)))
+
(define*-public (error->str err #:key (max-depth #f))
(if (record? err)
(let* ((type (record-type-descriptor err))
@@ -1151,6 +1193,28 @@
((&unconfirmed-provider)
(format #f (G_ "~s does not admit ~s as an identity provider")
(get 'subject) (get 'provider)))
+ ((&neither-identity-provider-nor-webid)
+ (format #f (G_ "~a is neither an identity provider (because ~a) nor a webid (because ~a)")
+ (uri->string (get 'uri))
+ (recurse (get 'why-not-identity-provider))
+ (recurse (get 'why-not-webid))))
+ ((&token-request-failed)
+ (format #f (G_ "the token request failed (because ~a)")
+ (recurse (get 'cause))))
+ ((&profile-not-found)
+ (format #f (G_ "you don’t have a refresh token for identity ~a certified by ~a in ~s")
+ (uri->string (get 'webid))
+ (uri->string (get 'iss))
+ (get 'dir)))
+ ((&no-provider-candidates)
+ (format #f (G_ "all identity provider candidates for ~a failed: ~a")
+ (uri->string (get 'webid))
+ (string-join
+ (map (lambda (cause)
+ (format #f (G_ "~s failed (because ~a)")
+ (uri->string (car cause)) (recurse (cdr cause))))
+ (get 'causes))
+ (G_ ", "))))
((&compound-exception)
(let ((components (get 'components)))
(if (null? components)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0ea5390..3cd7fed 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -37,7 +37,9 @@ TESTS = %reldir%/load-library.scm \
%reldir%/token-endpoint-issue.scm \
%reldir%/token-endpoint-refresh.scm \
%reldir%/provider-confirmation.scm \
- %reldir%/resource-server.scm
+ %reldir%/resource-server.scm \
+ %reldir%/client-authorization.scm \
+ %reldir%/client-token.scm
EXTRA_DIST += $(TESTS) %reldir%/ChangeLog
diff --git a/tests/client-authorization.scm b/tests/client-authorization.scm
new file mode 100644
index 0000000..ed02edf
--- /dev/null
+++ b/tests/client-authorization.scm
@@ -0,0 +1,118 @@
+(use-modules (webid-oidc client)
+ (webid-oidc testing)
+ ((webid-oidc stubs) #:prefix stubs:)
+ (web uri)
+ (web response)
+ (srfi srfi-19)
+ (ice-9 optargs)
+ (ice-9 receive)
+ (ice-9 hash-table))
+
+;; We need to test different things.
+
+;; 1. It works when passed a host
+;; 2. It works when passed a webid with foreign identity providers
+;; 3. It works when passed a webid without foreign identity providers
+
+(with-test-environment
+ "client-authorization"
+ (lambda ()
+ (define* (http-get uri #:key (headers '()))
+ (cond
+ ;; 1. We pass a host name
+ ((equal? uri (string->uri "https://case-1.client-authorization.scm/.well-known/openid-configuration"))
+ (values
+ (build-response #:headers `((content-type application/json)))
+ (stubs:scm->json-string
+ `((jwks_uri . "https://case-1.client-authorization.scm/keys")
+ (authorization_endpoint . "https://case-1.client-authorization.scm/authorize")
+ (token_endpoint . "https://case-1.client-authorization.scm/token")))))
+ ;; It’s not a webid
+ ((equal? uri (string->uri "https://case-1.client-authorization.scm"))
+ (values
+ (build-response #:code 404 #:reason-phrase "Not Found")
+ #f))
+ ;; 2. We first dereference the webid
+ ((equal? uri (string->uri "https://case-2.client-authorization.scm/profile/card#me"))
+ (values
+ (build-response #:headers `((content-type text/turtle)))
+ "<#me> <http://www.w3.org/ns/solid/terms#oidcIssuer> <https://one.identity.provider>, <https://another.identity.provider> ."))
+ ;; and we get the config of all IPs
+ ((equal? uri (string->uri "https://case-2.client-authorization.scm/.well-known/openid-configuration"))
+ (values
+ (build-response #:headers `((content-type application/json)))
+ (stubs:scm->json-string
+ `((jwks_uri . "https://case-2.client-authorization.scm/keys")
+ (authorization_endpoint . "https://case-2.client-authorization.scm/authorize")
+ (token_endpoint . "https://case-2.client-authorization.scm/token")))))
+ ((equal? uri (string->uri "https://one.identity.provider/.well-known/openid-configuration"))
+ (values
+ (build-response #:headers `((content-type application/json)))
+ (stubs:scm->json-string
+ `((jwks_uri . "https://one.identity.provider/keys")
+ (authorization_endpoint . "https://one.identity.provider/authorize")
+ (token_endpoint . "https://one.identity.provider/token")))))
+ ((equal? uri (string->uri "https://another.identity.provider/.well-known/openid-configuration"))
+ (values
+ (build-response #:headers `((content-type application/json)))
+ (stubs:scm->json-string
+ `((jwks_uri . "https://another.identity.provider/keys")
+ (authorization_endpoint . "https://another.identity.provider/authorize")
+ (token_endpoint . "https://another.identity.provider/token")))))
+ ;; 3. The webid has no IPs.
+ ((equal? uri (string->uri "https://case-3.client-authorization.scm/profile/card#me"))
+ (values
+ (build-response #:headers `((content-type text/turtle)))
+ ""))
+ ;; so we query the host of the webid.
+ ((equal? uri (string->uri "https://case-3.client-authorization.scm/.well-known/openid-configuration"))
+ (values
+ (build-response #:headers `((content-type application/json)))
+ (stubs:scm->json-string
+ `((jwks_uri . "https://case-3.client-authorization.scm/keys")
+ (authorization_endpoint . "https://case-3.client-authorization.scm/authorize")
+ (token_endpoint . "https://case-3.client-authorization.scm/token")))))
+ (else
+ (format (current-error-port) "Unexpected GET query of URI ~a.\n" (uri->string uri))
+ (exit 1))))
+ (let ((case-1 (authorize "case-1.client-authorization.scm"
+ #:client-id "https://app.client-authorization.scm"
+ #:redirect-uri "https://app.client-authorization.scm/redirected"
+ #:state "integrity&check"
+ #:http-get http-get))
+ (case-2 (authorize "https://case-2.client-authorization.scm/profile/card#me"
+ #:client-id "https://app.client-authorization.scm"
+ #:redirect-uri "https://app.client-authorization.scm/redirected"
+ #:state "integrity&check"
+ #:http-get http-get))
+ (case-3 (authorize "https://case-3.client-authorization.scm/profile/card#me"
+ #:client-id "https://app.client-authorization.scm"
+ #:redirect-uri "https://app.client-authorization.scm/redirected"
+ #:state "integrity&check"
+ #:http-get http-get))
+ (expected-1
+ `(("https://case-1.client-authorization.scm"
+ . ,(string->uri "https://case-1.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check"))))
+ (expected-2
+ `(("https://case-2.client-authorization.scm"
+ . ,(string->uri "https://case-2.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check"))
+ ("https://one.identity.provider"
+ . ,(string->uri "https://one.identity.provider/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check"))
+ ("https://another.identity.provider"
+ . ,(string->uri "https://another.identity.provider/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check"))))
+ (expected-3
+ `(("https://case-3.client-authorization.scm"
+ . ,(string->uri "https://case-3.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check")))))
+ (unless (equal? case-1 expected-1)
+ (format (current-error-port) "Case 1 failed:\n~s\n~s\n\n"
+ case-1 expected-1)
+ (exit 2))
+ (unless (equal? (hash-map->list cons (alist->hash-table case-2))
+ (hash-map->list cons (alist->hash-table expected-2)))
+ (format (current-error-port) "Case 2 failed:\n~s\n~s\n\n"
+ case-2 expected-2)
+ (exit 3))
+ (unless (equal? case-3 expected-3)
+ (format (current-error-port) "Case 3 failed:\n~s\n~s\n\n"
+ case-3 expected-3)
+ (exit 4)))))
diff --git a/tests/client-token.scm b/tests/client-token.scm
new file mode 100644
index 0000000..02f5ec7
--- /dev/null
+++ b/tests/client-token.scm
@@ -0,0 +1,121 @@
+(use-modules (webid-oidc client)
+ (webid-oidc testing)
+ (webid-oidc token-endpoint)
+ (webid-oidc jwk)
+ (webid-oidc jti)
+ (webid-oidc authorization-code)
+ (webid-oidc oidc-configuration)
+ (webid-oidc jws)
+ (webid-oidc oidc-id-token)
+ (web uri)
+ (web request)
+ (web response)
+ (srfi srfi-19)
+ (ice-9 optargs)
+ (ice-9 receive)
+ (ice-9 hash-table))
+
+(with-test-environment
+ "client-token"
+ (lambda ()
+ (define the-current-time 0)
+ (define issuer-key (generate-key #:n-size 2048))
+ (define issuer-configuration
+ (make-oidc-configuration
+ "https://issuer.client-token.scm/keys"
+ "https://issuer.client-token.scm/authorize"
+ "https://issuer.client-token.scm/token"))
+ (define token-endpoint (make-token-endpoint
+ (string->uri "https://issuer.client-token.scm/token")
+ (string->uri "https://issuer.client-token.scm")
+ 'RS256
+ issuer-key
+ 3600 ;; 1 hour
+ (make-jti-list)
+ #:current-time (lambda () the-current-time)))
+ (define client-key (generate-key #:n-size 2048))
+ (define authorization-code
+ (issue-authorization-code 'RS256 issuer-key 120
+ (string->uri "https://client-token.scm/profile/card#me")
+ (string->uri "https://app.client-token.scm/app#id")))
+ (define* (http-get uri #:key (headers '()))
+ (cond
+ ((equal? uri (string->uri "https://issuer.client-token.scm/.well-known/openid-configuration"))
+ (serve-oidc-configuration
+ (time-utc->date (make-time time-utc 0 (+ the-current-time 3600)))
+ issuer-configuration))
+ ((equal? uri (string->uri "https://issuer.client-token.scm/keys"))
+ (serve-jwks
+ (time-utc->date (make-time time-utc 0 (+ the-current-time 3600)))
+ (make-jwks (list issuer-key))))
+ (else
+ (format (current-error-port) "GET request to ~a: error.\n" (uri->string uri))
+ (exit 1))))
+ (define* (http-post uri #:key (body #f) (headers '()))
+ (unless (equal? uri (oidc-configuration-token-endpoint issuer-configuration))
+ (format (current-error-port)
+ "Wrong URI for token negociation: ~a (expected ~a).\n"
+ (uri->string uri)
+ (uri->string
+ (oidc-configuration-token-endpoint
+ issuer-configuration)))
+ (exit 2))
+ (unless (equal? body (format #f "grant_type=authorization_code&code=~a"
+ authorization-code))
+ (format (current-error-port)
+ "Wrong body: ~s\n" body)
+ (exit 3))
+ (unless (equal?
+ (assoc-ref headers 'content-type)
+ '(application/x-www-form-urlencoded))
+ (format (current-error-port)
+ "Wrong content type: ~s\n" (assoc-ref headers 'content-type))
+ (exit 4))
+ (let ((request
+ (build-request uri
+ #:method 'POST
+ #:headers headers
+ #:port (open-input-string body)))
+ (request-body body))
+ (token-endpoint request request-body)))
+ (let ((response
+ (token "https://issuer.client-token.scm"
+ client-key
+ #:authorization-code authorization-code
+ #:http-get http-get
+ #:http-post http-post
+ #:current-time (lambda () the-current-time))))
+ (let ((id-token (assq-ref response 'id_token))
+ (access-token (assq-ref response 'access_token))
+ (token-type (assq-ref response 'token_type))
+ (token-expiration (assq-ref response 'expires_in))
+ (refresh-token (assq-ref response 'refresh_token)))
+ (let ((id-token-dec (id-token-decode id-token #:http-get http-get))
+ (access-token-dec (jws-decode access-token (lambda (jws) issuer-key))))
+ (unless id-token-dec
+ (format (current-error-port) "Could not decode the ID token from ~s (~s)"
+ id-token response)
+ (exit 5))
+ (unless access-token-dec
+ (format (current-error-port) "Could not decode the access token from ~s (~s)"
+ access-token response)
+ (exit 6))
+ (unless refresh-token
+ (format (current-error-port) "There does not seem to be a refresh token in ~s"
+ response)
+ (exit 6))
+ (unless (equal? (id-token-webid id-token-dec)
+ (string->uri "https://client-token.scm/profile/card#me"))
+ (exit 7))
+ (unless (equal? (id-token-iss id-token-dec)
+ (string->uri "https://issuer.client-token.scm"))
+ (exit 8))
+ (unless (equal? (id-token-aud id-token-dec)
+ (string->uri "https://app.client-token.scm/app#id"))
+ (exit 9))
+ ;; It’s not the job of the client to check that the access
+ ;; token is correct; TODO: add a check with a resource
+ ;; server.
+
+ ;; TODO: try to negociate a refresh token.
+ )))))