Skip to content

Commit

Permalink
refactor(playground): integrate playground with client js sdk
Browse files Browse the repository at this point in the history
integrate playground with client js sdk
  • Loading branch information
simeng-li committed Nov 3, 2021
1 parent 5a0b46a commit 498dcdb
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 128 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
# production
/packages/*/dist
/packages/*/lib
/packages/*/build

# logs
logs
Expand Down
1 change: 1 addition & 0 deletions packages/playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Create `.env` file with the following content:
```env
RAZZLE_OIDC_BASE_URL=https://logto.dev/oidc
```

Then

```bash
Expand Down
12 changes: 12 additions & 0 deletions packages/playground/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Need to disable following rulus to mock text-decode/text-encoder and crypto for jsdom
// https://github.com/jsdom/jsdom/issues/1612
/* eslint-disable @silverhand/fp/no-mutation */
/* eslint-disable unicorn/prefer-module */
const { Crypto } = require('@peculiar/webcrypto');
const fetch = require('node-fetch');
const { TextDecoder, TextEncoder } = require('text-encoder');

global.crypto = new Crypto();
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
global.fetch = fetch;
6 changes: 5 additions & 1 deletion packages/playground/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@silverhand/playground",
"name": "@logto/playground",
"version": "0.1.0",
"license": "UNLICENSED",
"private": true,
Expand All @@ -14,6 +14,7 @@
"test": "razzle test --env=jsdom"
},
"dependencies": {
"@logto/client": "^0.0.1",
"@silverhand/essentials": "^1.1.0-rc.1",
"js-base64": "^3.6.1",
"ky": "^0.28.5",
Expand All @@ -25,6 +26,7 @@
},
"devDependencies": {
"@babel/core": "^7.14.6",
"@peculiar/webcrypto": "^1.1.7",
"@silverhand/eslint-config": "^0.2.2",
"@silverhand/eslint-config-react": "^0.2.2",
"@silverhand/ts-config": "^0.2.2",
Expand All @@ -41,12 +43,14 @@
"html-webpack-plugin": "^4.5.2",
"lint-staged": ">=10",
"mini-css-extract-plugin": "^0.9.0",
"node-fetch": "2.6.6",
"postcss": "^8.3.6",
"prettier": "^2.3.2",
"razzle": "^4.2.6",
"razzle-dev-utils": "^4.2.6",
"razzle-plugin-scss": "^4.2.6",
"stylelint": "^13.13.1",
"text-encoder": "^0.0.4",
"typescript": "^4.3.5",
"webpack": "^4.44.1",
"webpack-dev-server": "^3.11.2"
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/razzle.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ module.exports = {
'@/(.*)': '<rootDir>/src/$1',
};

config.setupFilesAfterEnv =['./jest.setup.js'];

return config;
},
};
38 changes: 31 additions & 7 deletions packages/playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import React from 'react';
import LogtoClient from '@logto/client';
import React, { useState, useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';

import { oidcDomain } from '@/consts/url';

import LogtoClientContext from './LogtoClientContext';
import Callback from './pages/Callback';
import Home from './pages/Home';
import './scss/normalized.scss';

const App = () => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/callback" component={Callback} />
</Switch>
);
const App = () => {
const [logtoClient, setLogtoClient] = useState<LogtoClient | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
(async () => {
const client = await LogtoClient.create({
domain: oidcDomain,
clientId: 'foo',
});
setLogtoClient(client);
setLoading(false);
})();
}, []);

return (
<Switch>
{!loading && (
<LogtoClientContext.Provider value={logtoClient}>
<Route exact path="/" component={Home} />
<Route exact path="/callback" component={Callback} />
</LogtoClientContext.Provider>
)}
</Switch>
);
};

export default App;
6 changes: 6 additions & 0 deletions packages/playground/src/LogtoClientContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import LogtoClient from '@logto/client';
import React from 'react';

const LogtoClientContext = React.createContext<LogtoClient | null>(null);

export default LogtoClientContext;
5 changes: 4 additions & 1 deletion packages/playground/src/consts/url.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export const baseUrl = `http://${String(process.env.HOST)}:${String(process.env.PORT)}`;
export const oidcUrl = process.env.RAZZLE_OIDC_BASE_URL ?? 'N/A';
export const oidcDomain = process.env.RAZZLE_OIDC_BASE_DOMAIN ?? 'N/A';
export const oidcUrl = process.env.RAZZLE_OIDC_BASE_DOMAIN
? `http://${process.env.RAZZLE_OIDC_BASE_DOMAIN}/oidc`
: 'N/A';
45 changes: 9 additions & 36 deletions packages/playground/src/pages/Callback/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,27 @@
import ky from 'ky';
import qs from 'query-string';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useContext } from 'react';

import { baseUrl, oidcUrl } from '@/consts/url';
import LogtoClientContext from '@/LogtoClientContext';

const Callback = () => {
const [error, setError] = useState<string>();
const logtoClient = useContext(LogtoClientContext);

useEffect(() => {
const { code, error } = qs.parse(location.search);

if (error) {
setError('Error:' + String(error));
return;
}

const verifier = localStorage.getItem('verifier');
const run = async () => {
if (typeof code !== 'string') {
setError("code doesn't show up in url");
return;
}

if (typeof verifier !== 'string') {
setError("verifier doesn't show up in localStorage");
(async () => {
if (!logtoClient) {
setError('LogtoClient not found');
return;
}

// For `application/x-www-form-urlencoded`
const body = new URLSearchParams();
body.set('redirect_uri', `${baseUrl}/callback`);
body.set('code', code);
body.set('grant_type', 'authorization_code');
body.set('client_id', 'foo');
body.set('code_verifier', verifier);

try {
const json = await ky.post(`${oidcUrl}/token`, { body }).json();

localStorage.setItem('auth', JSON.stringify(json));
localStorage.removeItem('verifier');
await logtoClient.handleCallback(location.href);
window.location.assign('/');
} catch (error: unknown) {
setError(String(error));
console.error(error);
}
};

void run();
}, []);
})();
}, [logtoClient]);

return (
<div style={{ textAlign: 'center' }}>
Expand Down
100 changes: 18 additions & 82 deletions packages/playground/src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,27 @@
import { conditional } from '@silverhand/essentials/lib/utilities/conditional.js';
import { fromUint8Array } from 'js-base64';
import ky from 'ky';
import qs from 'query-string';
import React, { useCallback } from 'react';
import { number, object, string } from 'zod';
import React, { useCallback, useMemo, useContext } from 'react';

import { baseUrl, oidcUrl } from '@/consts/url';
import LogtoClientContext from '@/LogtoClientContext';
import { baseUrl } from '@/consts/url';

import styles from './index.module.scss';

function base64URLEncode(array: Uint8Array) {
return fromUint8Array(array).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

const Auth = object({
access_token: string(),
refresh_token: string(),
id_token: string(),
expires_in: number(),
scope: string(),
token_type: string(),
});

const Home = () => {
const auth = localStorage.getItem('auth');
const parsed = conditional(auth && Auth.parse(JSON.parse(auth)));
const isAuthed = Boolean(auth);

const signIn = useCallback(async () => {
const verifier = base64URLEncode(crypto.getRandomValues(new Uint8Array(32)));
const encoded = new TextEncoder().encode(verifier);
const challenge = base64URLEncode(
new Uint8Array(await crypto.subtle.digest('SHA-256', encoded))
);

localStorage.setItem('verifier', verifier);
console.log(challenge, verifier);

const parameters = qs.stringify(
{
response_type: 'code',
redirect_uri: `${baseUrl}/callback`,
client_id: 'foo',
scope: 'openid%20offline_access',
prompt: 'consent',
code_challenge: challenge,
code_challenge_method: 'S256',
resource: 'https://api.logto.io',
},
{ encode: false }
);

window.location.assign(`${oidcUrl}/auth?${parameters}`);
}, []);
const logtoClient = useContext(LogtoClientContext);

const signOut = useCallback(async () => {
if (parsed?.access_token) {
const body = new URLSearchParams();
body.set('token', String(parsed.access_token));
body.set('client_id', 'foo');
await ky.post(`${oidcUrl}/token/revocation`, { body });
}
if (!logtoClient) {
throw new Error('no logto client found');
}

if (parsed?.refresh_token) {
const body = new URLSearchParams();
body.set('token', String(parsed.refresh_token));
body.set('client_id', 'foo');
await ky.post(`${oidcUrl}/token/revocation`, { body });
}
const isAuthed = useMemo(() => logtoClient.isAuthenticated(), [logtoClient]);
const claims = isAuthed && logtoClient.getClaims();

localStorage.removeItem('auth');
const parameters = qs.stringify(
{ id_token_hint: parsed?.id_token, post_logout_redirect_uri: baseUrl },
{ encode: false }
);
const signIn = useCallback(() => {
void logtoClient.loginWithRedirect(`${baseUrl}/callback`);
}, [logtoClient]);

window.location.assign(`${oidcUrl}/session/end?${parameters}`);
}, [parsed?.access_token, parsed?.id_token, parsed?.refresh_token]);
const signOut = useCallback(() => {
logtoClient.logout(baseUrl);
}, [logtoClient]);

return (
<div className={styles.wrapper}>
Expand All @@ -91,7 +36,7 @@ const Home = () => {
Sign Out
</button>
)}
{isAuthed && parsed && (
{claims && (
<table className={styles.table}>
<thead>
<tr>
Expand All @@ -100,19 +45,10 @@ const Home = () => {
</tr>
</thead>
<tbody>
{(
[
'access_token',
'expires_in',
'refresh_token',
'id_token',
'scope',
'token_type',
] as const
).map((key) => (
{Object.keys(claims).map((key) => (
<tr key={key}>
<td>{key}</td>
<td>{parsed[key]}</td>
<td>{claims[key as keyof typeof claims]}</td>
</tr>
))}
</tbody>
Expand Down
10 changes: 9 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 498dcdb

Please sign in to comment.