update to a more specific usage
This commit is contained in:
parent
afe1371fb1
commit
bfd9558e34
20 changed files with 91 additions and 629 deletions
|
@ -12,3 +12,6 @@ You can serve it with your preferred local file server.
|
||||||
python -m SimpleHTTPServer 9999
|
python -m SimpleHTTPServer 9999
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
Generate self certificate
|
||||||
|
|
||||||
|
|
21
cert.pem
21
cert.pem
|
@ -1,21 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDfjCCAmYCCQCkePR4pS0CNjANBgkqhkiG9w0BAQsFADCBgDELMAkGA1UEBhMC
|
|
||||||
RlIxDzANBgNVBAgMBkZyYW5jZTEQMA4GA1UEBwwHTW91Z2luczEOMAwGA1UECgwF
|
|
||||||
Q2lzY28xDTALBgNVBAsMBElST0gxDDAKBgNVBAMMA0NUUjEhMB8GCSqGSIb3DQEJ
|
|
||||||
ARYSeWFlc3Bvc2lAY2lzY28uY29tMB4XDTE4MTAxMTA4MzYyNloXDTE5MTAxMTA4
|
|
||||||
MzYyNlowgYAxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxEDAOBgNVBAcM
|
|
||||||
B01vdWdpbnMxDjAMBgNVBAoMBUNpc2NvMQ0wCwYDVQQLDARJUk9IMQwwCgYDVQQD
|
|
||||||
DANDVFIxITAfBgkqhkiG9w0BCQEWEnlhZXNwb3NpQGNpc2NvLmNvbTCCASIwDQYJ
|
|
||||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcScS0YDCjJdTMbGFYCLm6ixcNtZJAp
|
|
||||||
qBM3hB5OuSOY05PXfovefvjPQLgw1BEoIDyzwy/5ZMWgxsFsqqMaLCAWcjuOvmRs
|
|
||||||
1/RKznjkiPGviZ0CKzeeQ+LY9SdsRtwNM0abB4Od20EjkMRu7Mk1cm5zG/8y+ONn
|
|
||||||
T8ZChDFT3wPWmKbftL8zOANe9n/eX/bWutc1wcu4NehunZliLkNPnqkPlHe6KZqM
|
|
||||||
O4llE+8H2CeNFdUVk83HXFUVX0La3hQmjN2JZwEkYyjUzejb1ic9+EWTeNhBHYV6
|
|
||||||
98yJaj3AB4AFKlmFrHPJOw8jSlxol34w7jK3vQeJ+XsU79NjYyctNgsCAwEAATAN
|
|
||||||
BgkqhkiG9w0BAQsFAAOCAQEAU+L1NtOw9FdiDx1dgdwtShajoHBNGG8ugy5xg7qF
|
|
||||||
kJbnhqqSoTd2wibQSdXhBcg0BLaRnjfg3J30X+LmHE29wvIxauHlGH3eTzaNXRH1
|
|
||||||
Re8IhSGMH7XNyMHvJN89DhsxSbS86zyBz08patNwYmHvyDohDffipMcd3mnOrjcs
|
|
||||||
gwp4t2/HGpDHSENkxgEm7pKBouBWgbAVxKDoLRyfoBG/jts+0d+c0B4DDmIdgeY2
|
|
||||||
W8sbWQoPIFxs6I1cJOuAr86D7YvW2JbaPSy3RN9QnOWeKnNEgK8NTSZuT7ICnFwA
|
|
||||||
lUGLT2SInc1Np4NfOWH6LgmRxgnb2mPr2LBf5NqliqdILg==
|
|
||||||
-----END CERTIFICATE-----
|
|
5
cert/.gitignore
vendored
Normal file
5
cert/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.srl
|
||||||
|
*.csr
|
26
cert/gen-new-cert.sh
Executable file
26
cert/gen-new-cert.sh
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
echo "Please provide a root certificate name you trust as first parameter"
|
||||||
|
echo "For example: $0 rootCA"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rootca=$1
|
||||||
|
|
||||||
|
echo "Create certificate key for localhost: server.key"
|
||||||
|
openssl req -new -sha256 -nodes -out server.csr \
|
||||||
|
-newkey rsa:2048 \
|
||||||
|
-keyout server.key \
|
||||||
|
-config server.csr.cnf
|
||||||
|
|
||||||
|
echo "Create SSL certificate for localhost: server.crt"
|
||||||
|
openssl x509 -req \
|
||||||
|
-in server.csr \
|
||||||
|
-CA $rootca.pem \
|
||||||
|
-CAkey $rootca.key \
|
||||||
|
-CAcreateserial \
|
||||||
|
-out server.crt \
|
||||||
|
-days 500 \
|
||||||
|
-sha256 \
|
||||||
|
-extfile v3.ext
|
12
cert/gen-new-root-cert.sh
Executable file
12
cert/gen-new-root-cert.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
name=${1:-rootCA}
|
||||||
|
|
||||||
|
echo "Generate root certificate key ${name}.key"
|
||||||
|
openssl genrsa -des3 -out ${name}.key 2048
|
||||||
|
|
||||||
|
echo "Generate new root SSL certificate: ${name}.pem"
|
||||||
|
openssl req -x509 -new -nodes -key ${name}.key -sha256 -days 1024 -out ${name}.pem
|
||||||
|
|
||||||
|
echo "You should now trust the root SSL certificate"
|
||||||
|
echo " Example: https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/#step-2-trust-the-root-ssl-certificate"
|
14
cert/server.csr.cnf
Normal file
14
cert/server.csr.cnf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
distinguished_name = dn
|
||||||
|
|
||||||
|
[dn]
|
||||||
|
C=US
|
||||||
|
ST=LocalState
|
||||||
|
L=LocalCity
|
||||||
|
O=LocalOrganization
|
||||||
|
OU=LocalOrganizationUnit
|
||||||
|
emailAddress=nobody@dev.null
|
||||||
|
CN = localhost
|
7
cert/v3.ext
Normal file
7
cert/v3.ext
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = localhost
|
204
code.html
204
code.html
|
@ -1,204 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>OAuth2 Demo Login</title>
|
|
||||||
<link rel="stylesheet" href="brutalist.css" />
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
|
||||||
<script src="./infos.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
<h1>Yolo App login page</h1>
|
|
||||||
<p>Authorization process done!</p>
|
|
||||||
<a href="/index.html">← go back to main page</a>
|
|
||||||
<h2>Authorization Status</h2>
|
|
||||||
<pre class="code" id="authorization_status"></pre>
|
|
||||||
<div id="error-info"></div>
|
|
||||||
<h3>State</h3>
|
|
||||||
The process should also return the state provided.
|
|
||||||
<pre class="code" id="state-param"></pre>
|
|
||||||
<div id="oncode">
|
|
||||||
<h2>Code</h2>
|
|
||||||
<p>The code is generated by the Authentication server and send back
|
|
||||||
to the client via the resource's owner user-agent</p>
|
|
||||||
<p>For us, it is a JWT:</p>
|
|
||||||
<pre class="code" id="code-param"></pre>
|
|
||||||
<p>Which once decoded is:</p>
|
|
||||||
<pre class="code" id="code-token"></pre>
|
|
||||||
<h2>Tokens</h2>
|
|
||||||
<p> Now the client server need to retrieve an <em>Access Token</em>
|
|
||||||
and a <em>Refresh Token</em> by using that code.</p>
|
|
||||||
<p>To achieve that the client will make a call to <code>/token</code>
|
|
||||||
using a basic auth creds</p>
|
|
||||||
<p>You have about 10 mins to retrieve them.
|
|
||||||
Unlike in this demo, that <strong>MUST</strong> be done server side.</p>
|
|
||||||
<div class="button"
|
|
||||||
onclick="getTokensFromCode();">
|
|
||||||
Get Access & Refresh Tokens from Code
|
|
||||||
</div>
|
|
||||||
<h3>Response from <code>/token</code></h3>
|
|
||||||
<p>token endpoint URL: <code id="urltoken" class="code">Nothing yet.</code></p>
|
|
||||||
<pre id="token" class="code">Nothing yet.</pre>
|
|
||||||
<h3>decoded access-token</h3>
|
|
||||||
<pre id="access-token" class="code">Nothing yet.</pre>
|
|
||||||
<h3>decoded refresh-token</h3>
|
|
||||||
<pre id="refresh-token" class="code">Nothing yet.</pre>
|
|
||||||
<h2>Using the API</h2>
|
|
||||||
<div class="button"
|
|
||||||
onclick="makeApiCall();">
|
|
||||||
Make an API call with the access token
|
|
||||||
</div>
|
|
||||||
<p>API URL: <code id="apiurl" class="code">Nothing yet.</code></p>
|
|
||||||
<pre id="apiresponse" class="code">Nothing yet.</pre>
|
|
||||||
<h2>Getting new access token without user interaction</h2>
|
|
||||||
<p> The access tokens are the only tokens which are able to talk to the
|
|
||||||
Visibility API.</p>
|
|
||||||
<p>Access tokens live a short time (about 10 min to 1 hour)</p>
|
|
||||||
<p>This is why the client must require a new access token using its
|
|
||||||
<em>refresh token</em>.
|
|
||||||
That is just making another call to <code>/token</code>
|
|
||||||
But with different parameters. </p>
|
|
||||||
<div class="button"
|
|
||||||
onclick="getAccessToken();">
|
|
||||||
Get Access Tokens from Refresh Token
|
|
||||||
</div>
|
|
||||||
<pre id="refreshed" class="code">Nothing yet.</pre>
|
|
||||||
<pre id="refreshed-access-token" class="code">Nothing yet.</pre>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
/* ----- */
|
|
||||||
function getJsonFromUrl() {
|
|
||||||
var query = location.search.substr(1);
|
|
||||||
var result = {};
|
|
||||||
query.split("&").forEach(function(part) {
|
|
||||||
var item = part.split("=");
|
|
||||||
result[item[0]] = decodeURIComponent(item[1]);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
var params=getJsonFromUrl();
|
|
||||||
var authstatus="";
|
|
||||||
if (params.error) {
|
|
||||||
authstatus = "REFUSED: " + params.error;
|
|
||||||
if (params.error_description) {
|
|
||||||
authstatus += "\n\n" + params.error_description;
|
|
||||||
}
|
|
||||||
if (params.error_uri) {
|
|
||||||
authstatus += "<a href='" + params.error_uri + "'>" + params.error_uri + "</a>";
|
|
||||||
}
|
|
||||||
$('#authorization_status').addClass('refused');
|
|
||||||
$('#oncode').hide();
|
|
||||||
} else {
|
|
||||||
if (params.code) {
|
|
||||||
authstatus = "AUTHORIZED" ;
|
|
||||||
$('#authorization_status').addClass('authorized');
|
|
||||||
} else {
|
|
||||||
authstatus = "UNKNOWN" ;
|
|
||||||
$('#oncode').hide();
|
|
||||||
$('#state-param').html("No state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#urltoken').html( oauthServerTokenUrl );
|
|
||||||
$('#apiurl').html( resourceProviderTestEndpoint );
|
|
||||||
$('#authorization_status').html( authstatus );
|
|
||||||
$('#state-param').html(params.state);
|
|
||||||
if (params.error_uri) {
|
|
||||||
var erroruri=decodeURIComponent(params.error_uri);
|
|
||||||
$('#error-info').html("<p>You can have more informations here:</p><a href=\"" + erroruri + "\" target=\"_blank\">" + erroruri + "</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
function jwtDecode(t) {
|
|
||||||
let token = {};
|
|
||||||
token.raw = t;
|
|
||||||
token.header = JSON.parse(window.atob(t.split('.')[0]));
|
|
||||||
token.payload = JSON.parse(window.atob(t.split('.')[1]));
|
|
||||||
return (token)
|
|
||||||
}
|
|
||||||
var jwt=jwtDecode(params.code).payload;
|
|
||||||
$('#code-param').html(params.code);
|
|
||||||
$('#code-token').html(JSON.stringify(jwt,null,2));
|
|
||||||
var refreshToken="";
|
|
||||||
var accessToken="";
|
|
||||||
var getTokensFromCode = function() {
|
|
||||||
var tokparams={
|
|
||||||
"code":params.code
|
|
||||||
, "redirect_uri":redirect_uri
|
|
||||||
, "scope":scope
|
|
||||||
, "client_id":client_id
|
|
||||||
, "grant_type":"authorization_code"
|
|
||||||
};
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#token').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#token").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));
|
|
||||||
$("#access-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.access_token).payload,null,2) );
|
|
||||||
$("#refresh-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.refresh_token).payload,null,2) );
|
|
||||||
accessToken=jqXHR.responseJSON.access_token;
|
|
||||||
refreshToken=jqXHR.responseJSON.refresh_token;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
type: "POST"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Basic " + btoa(client_id + ":" + client_password))}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: oauthServerTokenUrl
|
|
||||||
, data: tokparams
|
|
||||||
, contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var getAccessToken = function() {
|
|
||||||
var tokparams={
|
|
||||||
"refresh_token":refreshToken
|
|
||||||
, "scope":scope
|
|
||||||
, "client_id":client_id
|
|
||||||
, "grant_type":"refresh_token"
|
|
||||||
};
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#refreshed').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2))}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#refreshed").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));
|
|
||||||
$("#refreshed-access-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.access_token).payload,null,2) );
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
type: "POST"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Basic " + btoa(client_id + ":" + client_password))}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: oauthServerTokenUrl
|
|
||||||
, data: tokparams
|
|
||||||
, contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var makeApiCall = function() {
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#apiresponse').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2))}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#apiresponse").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));}
|
|
||||||
$.ajax({
|
|
||||||
type: "GET"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Bearer " + accessToken)}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: resourceProviderTestEndpoint
|
|
||||||
, contentType: 'application/json'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
128
implicit.html
128
implicit.html
|
@ -1,128 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>OAuth2 Demo Login</title>
|
|
||||||
<link rel="stylesheet" href="brutalist.css" />
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
|
||||||
<script src="./infos.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
<h1>Yolo App login page</h1>
|
|
||||||
<p>Authorization process done!</p>
|
|
||||||
<a href="/index.html">← go back to main page</a>
|
|
||||||
<h2>Authorization Status</h2>
|
|
||||||
<pre class="code" id="authorization_status"></pre>
|
|
||||||
<div id="error-info"></div>
|
|
||||||
<h3>State</h3>
|
|
||||||
The process should also return the state provided.
|
|
||||||
<pre class="code" id="state-param"></pre>
|
|
||||||
<div id="onaccesstoken">
|
|
||||||
<h2>Access Token</h2>
|
|
||||||
<p>The access token is generated by the Authentication server and
|
|
||||||
send back to the client via the resource's owner user-agent</p>
|
|
||||||
<p>Access tokens live a short time (about 10 min to 1 hour)</p>
|
|
||||||
<p>For us, it is a JWT:</p>
|
|
||||||
<pre class="code" id="accesstoken-param"></pre>
|
|
||||||
<p>Which once decoded is:</p>
|
|
||||||
<pre class="code" id="accesstoken-token"></pre>
|
|
||||||
<h2>Using the API</h2>
|
|
||||||
<h2>API Call</h2>
|
|
||||||
<p>API URL: <code id="apiurl" class="code">Nothing yet.</code></p>
|
|
||||||
<div class="button"
|
|
||||||
onclick="makeApiCall();">
|
|
||||||
Make an API call with the access token
|
|
||||||
</div>
|
|
||||||
<p>API URL: <code id="apiurl" class="code">Nothing yet.</code></p>
|
|
||||||
<pre id="apiresponse" class="code">Nothing yet.</pre>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
/* ----- */
|
|
||||||
function getJsonFromUrl() {
|
|
||||||
var query = location.search.substr(1);
|
|
||||||
var result = {};
|
|
||||||
query.split("&").forEach(function(part) {
|
|
||||||
var item = part.split("=");
|
|
||||||
result[item[0]] = decodeURIComponent(item[1]);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
function getHashParams() {
|
|
||||||
var hashParams={};
|
|
||||||
var e,
|
|
||||||
a = /\+/g, // Regex for replacing addition symbol with a space
|
|
||||||
r = /([^&;=]+)=?([^&;]*)/g,
|
|
||||||
d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
|
|
||||||
q = window.location.hash.substring(1);
|
|
||||||
while (e = r.exec(q))
|
|
||||||
hashParams[d(e[1])] = d(e[2]);
|
|
||||||
return hashParams;
|
|
||||||
}
|
|
||||||
var params=getHashParams();
|
|
||||||
var authstatus="";
|
|
||||||
if (params.error) {
|
|
||||||
authstatus = "REFUSED: " + params.error;
|
|
||||||
if (params.error_description) {
|
|
||||||
authstatus += "\n\n" + params.error_description;
|
|
||||||
}
|
|
||||||
if (params.invalid_parameter) {
|
|
||||||
authstatus += "\n\nInvalid parameter: " + params.invalid_parameter;
|
|
||||||
}
|
|
||||||
if (params.error_uri) {
|
|
||||||
authstatus += "\n\n<a href='" + decodeURIComponent(params.error_uri) + "'>" + decodeURIComponent(params.error_uri) + "</a>";
|
|
||||||
}
|
|
||||||
$('#authorization_status').addClass('refused');
|
|
||||||
$('#onaccesstoken').hide();
|
|
||||||
} else {
|
|
||||||
if (params.access_token) {
|
|
||||||
authstatus = "AUTHORIZED" ;
|
|
||||||
$('#authorization_status').addClass('authorized');
|
|
||||||
} else {
|
|
||||||
authstatus = "UNKNOWN" ;
|
|
||||||
$('#onaccesstoken').hide();
|
|
||||||
$('#state-param').html("No state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#urltoken').html( oauthServerTokenUrl );
|
|
||||||
$('#apiurl').html( resourceProviderTestEndpoint );
|
|
||||||
$('#authorization_status').html( authstatus );
|
|
||||||
$('#state-param').html(params.state);
|
|
||||||
if (params.error_uri) {
|
|
||||||
var erroruri=decodeURIComponent(params.error_uri);
|
|
||||||
$('#error-info').html("<p>You can have more informations here:</p><a href=\"" + erroruri + "\" target=\"_blank\">" + erroruri + "</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
function jwtDecode(t) {
|
|
||||||
let token = {};
|
|
||||||
token.raw = t;
|
|
||||||
token.header = JSON.parse(window.atob(t.split('.')[0]));
|
|
||||||
token.payload = JSON.parse(window.atob(t.split('.')[1]));
|
|
||||||
return (token)
|
|
||||||
}
|
|
||||||
var jwt=jwtDecode(params.access_token).payload;
|
|
||||||
$('#accesstoken-param').html(params.access_token);
|
|
||||||
$('#accesstoken-token').html(JSON.stringify(jwt,null,2));
|
|
||||||
var accessToken=params.access_token;
|
|
||||||
var makeApiCall = function() {
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#apiresponse').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2))}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#apiresponse").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));}
|
|
||||||
$.ajax({
|
|
||||||
type: "GET"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Bearer " + accessToken)}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: resourceProviderTestEndpoint
|
|
||||||
, contentType: 'application/json'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
1
info.js
Symbolic link
1
info.js
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
site/infos.js
|
14
infos.js
14
infos.js
|
@ -1,14 +0,0 @@
|
||||||
var oauthURLPrefix="http://localhost:9001";
|
|
||||||
var oauthServerUrl=oauthURLPrefix + "/iroh/oauth2/authorize";
|
|
||||||
var oauthServerTokenUrl=oauthURLPrefix + "/iroh/oauth2/token";
|
|
||||||
var resourceProviderTestEndpoint=oauthURLPrefix + "/iroh/iroh-ui-settings/whoami" ;
|
|
||||||
var response_type="code";
|
|
||||||
var client_id="localtest";
|
|
||||||
var client_password = "localpass";
|
|
||||||
var redirect_uri="http://localhost:9999/code.html";
|
|
||||||
var scopes=[ "private-intel"
|
|
||||||
, "ui-settings"
|
|
||||||
// , inexistant
|
|
||||||
];
|
|
||||||
var scope=scopes.join(" ");
|
|
||||||
var state="whatever";
|
|
BIN
infos.js.gpg
BIN
infos.js.gpg
Binary file not shown.
30
key.pem
30
key.pem
|
@ -1,30 +0,0 @@
|
||||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
|
||||||
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
|
|
||||||
Gi5TUJGHzcQrVY6FEQHS1pIL+A==
|
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
204
login.html
204
login.html
|
@ -1,204 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>OAuth2 Demo Login</title>
|
|
||||||
<link rel="stylesheet" href="brutalist.css" />
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
|
||||||
<script src="./infos.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
<h1>Yolo App login page</h1>
|
|
||||||
<p>Authorization process done!</p>
|
|
||||||
<a href="/index.html">← go back to main page</a>
|
|
||||||
<h2>Authorization Status</h2>
|
|
||||||
<pre class="code" id="authorization_status"></pre>
|
|
||||||
<div id="error-info"></div>
|
|
||||||
<h3>State</h3>
|
|
||||||
The process should also return the state provided.
|
|
||||||
<pre class="code" id="state-param"></pre>
|
|
||||||
<div id="oncode">
|
|
||||||
<h2>Code</h2>
|
|
||||||
<p>The code is generated by the Authentication server and send back
|
|
||||||
to the client via the resource's owner user-agent</p>
|
|
||||||
<p>For us, it is a JWT:</p>
|
|
||||||
<pre class="code" id="code-param"></pre>
|
|
||||||
<p>Which once decoded is:</p>
|
|
||||||
<pre class="code" id="code-token"></pre>
|
|
||||||
<h2>Tokens</h2>
|
|
||||||
<p> Now the client server need to retrieve an <em>Access Token</em>
|
|
||||||
and a <em>Refresh Token</em> by using that code.</p>
|
|
||||||
<p>To achieve that the client will make a call to <code>/token</code>
|
|
||||||
using a basic auth creds</p>
|
|
||||||
<p>You have about 10 mins to retrieve them.
|
|
||||||
Unlike in this demo, that <strong>MUST</strong> be done server side.</p>
|
|
||||||
<div class="button"
|
|
||||||
onclick="getTokensFromCode();">
|
|
||||||
Get Access & Refresh Tokens from Code
|
|
||||||
</div>
|
|
||||||
<h3>Response from <code>/token</code></h3>
|
|
||||||
<p>token endpoint URL: <code id="urltoken" class="code">Nothing yet.</code></p>
|
|
||||||
<pre id="token" class="code">Nothing yet.</pre>
|
|
||||||
<h3>decoded access-token</h3>
|
|
||||||
<pre id="access-token" class="code">Nothing yet.</pre>
|
|
||||||
<h3>decoded refresh-token</h3>
|
|
||||||
<pre id="refresh-token" class="code">Nothing yet.</pre>
|
|
||||||
<h2>Using the API</h2>
|
|
||||||
<div class="button"
|
|
||||||
onclick="makeApiCall();">
|
|
||||||
Make an API call with the access token
|
|
||||||
</div>
|
|
||||||
<p>API URL: <code id="apiurl" class="code">Nothing yet.</code></p>
|
|
||||||
<pre id="apiresponse" class="code">Nothing yet.</pre>
|
|
||||||
<h2>Getting new access token without user interaction</h2>
|
|
||||||
<p> The access tokens are the only tokens which are able to talk to the
|
|
||||||
Visibility API.</p>
|
|
||||||
<p>Access tokens live a short time (about 10 min to 1 hour)</p>
|
|
||||||
<p>This is why the client must require a new access token using its
|
|
||||||
<em>refresh token</em>.
|
|
||||||
That is just making another call to <code>/token</code>
|
|
||||||
But with different parameters. </p>
|
|
||||||
<div class="button"
|
|
||||||
onclick="getAccessToken();">
|
|
||||||
Get Access Tokens from Refresh Token
|
|
||||||
</div>
|
|
||||||
<pre id="refreshed" class="code">Nothing yet.</pre>
|
|
||||||
<pre id="refreshed-access-token" class="code">Nothing yet.</pre>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
/* ----- */
|
|
||||||
function getJsonFromUrl() {
|
|
||||||
var query = location.search.substr(1);
|
|
||||||
var result = {};
|
|
||||||
query.split("&").forEach(function(part) {
|
|
||||||
var item = part.split("=");
|
|
||||||
result[item[0]] = decodeURIComponent(item[1]);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
var params=getJsonFromUrl();
|
|
||||||
var authstatus="";
|
|
||||||
if (params.error) {
|
|
||||||
authstatus = "REFUSED: " + params.error;
|
|
||||||
if (params.error_description) {
|
|
||||||
authstatus += "\n\n" + params.error_description;
|
|
||||||
}
|
|
||||||
if (params.error_uri) {
|
|
||||||
authstatus += "<a href='" + params.error_uri + "'>" + params.error_uri + "</a>";
|
|
||||||
}
|
|
||||||
$('#authorization_status').addClass('refused');
|
|
||||||
$('#oncode').hide();
|
|
||||||
} else {
|
|
||||||
if (params.code) {
|
|
||||||
authstatus = "AUTHORIZED" ;
|
|
||||||
$('#authorization_status').addClass('authorized');
|
|
||||||
} else {
|
|
||||||
authstatus = "UNKNOWN" ;
|
|
||||||
$('#oncode').hide();
|
|
||||||
$('#state-param').html("No state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#urltoken').html( oauthServerTokenUrl );
|
|
||||||
$('#apiurl').html( resourceProviderTestEndpoint );
|
|
||||||
$('#authorization_status').html( authstatus );
|
|
||||||
$('#state-param').html(params.state);
|
|
||||||
if (params.error_uri) {
|
|
||||||
var erroruri=decodeURIComponent(params.error_uri);
|
|
||||||
$('#error-info').html("<p>You can have more informations here:</p><a href=\"" + erroruri + "\" target=\"_blank\">" + erroruri + "</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
function jwtDecode(t) {
|
|
||||||
let token = {};
|
|
||||||
token.raw = t;
|
|
||||||
token.header = JSON.parse(window.atob(t.split('.')[0]));
|
|
||||||
token.payload = JSON.parse(window.atob(t.split('.')[1]));
|
|
||||||
return (token)
|
|
||||||
}
|
|
||||||
var jwt=jwtDecode(params.code).payload;
|
|
||||||
$('#code-param').html(params.code);
|
|
||||||
$('#code-token').html(JSON.stringify(jwt,null,2));
|
|
||||||
var refreshToken="";
|
|
||||||
var accessToken="";
|
|
||||||
var getTokensFromCode = function() {
|
|
||||||
var tokparams={
|
|
||||||
"code":params.code
|
|
||||||
, "redirect_uri":redirect_uri
|
|
||||||
, "scope":scope
|
|
||||||
, "client_id":client_id
|
|
||||||
, "grant_type":"authorization_code"
|
|
||||||
};
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#token').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#token").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));
|
|
||||||
$("#access-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.access_token).payload,null,2) );
|
|
||||||
$("#refresh-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.refresh_token).payload,null,2) );
|
|
||||||
accessToken=jqXHR.responseJSON.access_token;
|
|
||||||
refreshToken=jqXHR.responseJSON.refresh_token;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
type: "POST"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Basic " + btoa(client_id + ":" + client_password))}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: oauthServerTokenUrl
|
|
||||||
, data: tokparams
|
|
||||||
, contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var getAccessToken = function() {
|
|
||||||
var tokparams={
|
|
||||||
"refresh_token":refreshToken
|
|
||||||
, "scope":scope
|
|
||||||
, "client_id":client_id
|
|
||||||
, "grant_type":"refresh_token"
|
|
||||||
};
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#refreshed').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2))}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#refreshed").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));
|
|
||||||
$("#refreshed-access-token").html( JSON.stringify(jwtDecode(jqXHR.responseJSON.access_token).payload,null,2) );
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
type: "POST"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Basic " + btoa(client_id + ":" + client_password))}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: oauthServerTokenUrl
|
|
||||||
, data: tokparams
|
|
||||||
, contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var makeApiCall = function() {
|
|
||||||
var onError = function(jqXHR,textStatus,errorThrown){
|
|
||||||
$('#apiresponse').html(errorThrown + " status: " + jqXHR.status
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2))}
|
|
||||||
var onSuccess = function(data,textStatus,jqXHR) {
|
|
||||||
$("#apiresponse").html(data
|
|
||||||
+ "\n---\n"
|
|
||||||
+ JSON.stringify(jqXHR.responseJSON,null,2));}
|
|
||||||
$.ajax({
|
|
||||||
type: "GET"
|
|
||||||
, beforeSend: function(request) {request.setRequestHeader("Authorization","Bearer " + accessToken)}
|
|
||||||
, success: onSuccess
|
|
||||||
, error: onError
|
|
||||||
, url: resourceProviderTestEndpoint
|
|
||||||
, contentType: 'application/json'
|
|
||||||
, crossDomain: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEAxxJxLRgMKMl1MxsYVgIubqLFw21kkCmoEzeEHk65I5jTk9d+
|
|
||||||
i95++M9AuDDUESggPLPDL/lkxaDGwWyqoxosIBZyO46+ZGzX9ErOeOSI8a+JnQIr
|
|
||||||
N55D4tj1J2xG3A0zRpsHg53bQSOQxG7syTVybnMb/zL442dPxkKEMVPfA9aYpt+0
|
|
||||||
vzM4A172f95f9ta61zXBy7g16G6dmWIuQ0+eqQ+Ud7opmow7iWUT7wfYJ40V1RWT
|
|
||||||
zcdcVRVfQtreFCaM3YlnASRjKNTN6NvWJz34RZN42EEdhXr3zIlqPcAHgAUqWYWs
|
|
||||||
c8k7DyNKXGiXfjDuMre9B4n5exTv02NjJy02CwIDAQABAoIBAQCmuaNgCQvl/RPT
|
|
||||||
QZYYMquArYLEMhJPSxQhRBW67GPSlGRfJdQugKIG8E1b9kkmNV0KeeO3gkAL5KOR
|
|
||||||
y9j90SWzmhkCGIMMyLv1NdHHHmAzSrkOboqTglGI4/wukFioY8XWFHEuoj7HR2ov
|
|
||||||
mldmDQVRy8Ze7enVSuldOshMJ9pX8x9bE6hnxBDsI6Vm8nJfnpZDi76UNKf+4F9c
|
|
||||||
d2GA6z31BsQOOFPb7OmTZ8nAg7Mn/6J6oW1h4PKq34qjxJdHfFtsjuB1h+zdsUMW
|
|
||||||
HlB521TUJqgOg9hUkhO9OcMEmigJnpe3FVOcEU+rlnQa4FVGpUhBdQzRPah4P6KO
|
|
||||||
iM+Lq1rBAoGBAPWVZQNXVK5KAjpbETVUIN3vyDaykGfLWo3gGb7+mDZyP4XQp9uI
|
|
||||||
+mULdBr4fbm2kY/ZWKTosLuOjdZd7UbD2ON9XOXZkxbBbV13J3iZMnkhNvvLoait
|
|
||||||
7fTg0Q3v8n5tmYWW5S4/Ixoef6rbl/Y+7qnrtyAV1ltpGM/B8mVrol6PAoGBAM+E
|
|
||||||
A5zxTp79vAYCljMFbzQEtW8wWUL/JvMetCozrgwDmSDgBuo8/2YH9AueKJE78tr4
|
|
||||||
ZiUFCRmJqaZbBnbVOvRehXiW5+OkWAaN+xQyr8Bw6PMn9GJ7fXNiKUQ+verQDFd1
|
|
||||||
vW9PwIdD0S7QTo7udzfSJxcTTvpkCMLVRzXUV27FAoGAVkyXYKMxuMV9HvCdjXXw
|
|
||||||
zszSOJZrNG47dhvIMFvbNVbUh7uZIQZzp0ptFIU5+Sb04+3mvpyebmpb4XbESNfN
|
|
||||||
wbNuD79zds2mFvAmSLTdDb/kSHt4ZYQpWKIgFJu7RT5ScqViB+xb83PAPVRJhFj9
|
|
||||||
GW0hvv6tgxXdiDSJb6ZxOnUCgYB3DzYHeId0Pv3+sMM+WLLZtI3oUmQURVIykP4r
|
|
||||||
bGVCbVoQ+5fcEs0x3ARQGhkKYsvOiFAKk14KMeqNEJKf4W0mwYhwjJxVBnieoh1Z
|
|
||||||
FE+z6NQullDnpBRw+/PTPPA35c/+6gWa9LXwpZ9B7a/036q08zUMTz6z+GryZe2q
|
|
||||||
W78iRQKBgQDC3sajFjIxLs5uXKMVIWhj8JXaDh35EhknTxOPIoXjoNUOn4YeALYs
|
|
||||||
tVm7rGmaLlG+R78mqWEPmWBNOG9PUCfyfaRWYrxHu0ZGirwK7g9sWaawpW5sfN0v
|
|
||||||
NztJc4vx/YlAS9SJvbW+uoPTgos4qvJj2Y03kgJ3oV793w+5FQMlqA==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
11
site/infos.js
Normal file
11
site/infos.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
var oauthURLPrefix="https://visibility.amp.cisco.com";
|
||||||
|
var oauthServerUrl=oauthURLPrefix + "/iroh/oauth2/authorize";
|
||||||
|
var oauthServerTokenUrl=oauthURLPrefix + "/iroh/oauth2/token";
|
||||||
|
var resourceProviderTestEndpoint=oauthURLPrefix + "/iroh/profile/whoami" ;
|
||||||
|
var response_type="code";
|
||||||
|
var client_id="client-3bb1e787-381d-4f12-bf32-e1158f200ddc";
|
||||||
|
var client_password = "CrXwg31_vnRHpjPXzgVzUFKHr6RO8GTL-iI8aDeUU3n48NtD7PFLhg";
|
||||||
|
var redirect_uri="https://localhost:5443/callback.html";
|
||||||
|
var scopes=[ "profile", "inspect" ];
|
||||||
|
var scope=scopes.join(" ");
|
||||||
|
var state="whatever=";
|
13
start-server.sh
Normal file → Executable file
13
start-server.sh
Normal file → Executable file
|
@ -1,2 +1,13 @@
|
||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
sws --local --no-auth . --port 5443 --certificate=cert.pem --key-file=nopasskey.pem -X 'Content-Type: text/html; charset=utf-8'
|
|
||||||
|
# https://github.com/derekelkins/sws
|
||||||
|
# version 0.4.2.0
|
||||||
|
# 1. Install stack: https://docs.haskellstack.org/en/stable/README/
|
||||||
|
# 2. Exec: stack install sws
|
||||||
|
|
||||||
|
sws --local \
|
||||||
|
--no-auth site \
|
||||||
|
--port 5443 \
|
||||||
|
--certificate=cert/server.crt \
|
||||||
|
--key-file=cert/server.key \
|
||||||
|
-X 'Content-Type: text/html; charset=utf-8'
|
||||||
|
|
Loading…
Reference in a new issue