mirror of
https://github.com/TECHNOFAB11/svelte-oidc.git
synced 2025-12-10 23:20:05 +01:00
feat: oidc-client.js based Svelte OidcComponent
follows a pattern similar to @dopry/svelte-auth0, but uses the more standards compliant oidc-client.js library.
This commit is contained in:
commit
4fd62abe31
25 changed files with 7069 additions and 0 deletions
20
.eslintrc.js
Normal file
20
.eslintrc.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
'cypress/globals': true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:cypress/recommended', 'prettier'],
|
||||
overrides: [
|
||||
{
|
||||
files: '*.svelte',
|
||||
processor: 'svelte3/svelte3',
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2019,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['svelte3', 'cypress'],
|
||||
};
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
**/dist/**
|
||||
**/node_modules/**
|
||||
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
/public/bundle.*
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
save-exact=true
|
||||
15
.prettierrc
Normal file
15
.prettierrc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true
|
||||
}
|
||||
79
README.md
Normal file
79
README.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# svelte-oidc
|
||||
|
||||
An Oidc Client Component for Svelte.
|
||||
|
||||
[Try out the demo](https://darrelopry.com/svelte-oidc/)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Setup an OIDC Sever
|
||||
* https://www.ory.sh/
|
||||
* https://www.keycloak.org/
|
||||
* https://www.okta.com/
|
||||
* http://auth0.com/
|
||||
|
||||
Get the authority and client_id
|
||||
|
||||
`npm install @dopry/svelte-oidc`
|
||||
|
||||
App.svelte
|
||||
```
|
||||
# App.svelte
|
||||
import {
|
||||
OidcContext,
|
||||
authError,
|
||||
authToken,
|
||||
idToken,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
login,
|
||||
logout,
|
||||
userInfo,
|
||||
} from '@dopry/svelte-oidc';
|
||||
</script>
|
||||
|
||||
<OidcContext domain="dev-hvw40i79.auth0.com" client_id="aOijZt2ug6Ovgzp0HXdF23B6zxwA6PaP">
|
||||
<button on:click|preventDefault='{() => login() }'>Login</button>
|
||||
<button on:click|preventDefault='{() => logout() }'>Logout</button><br />
|
||||
<pre>isLoading: {$isLoading}</pre>
|
||||
<pre>isAuthenticated: {$isAuthenticated}</pre>
|
||||
<pre>authToken: {$authToken}</pre>
|
||||
<pre>idToken: {$authToken}</pre>
|
||||
<pre>userInfo: {JSON.stringify($userInfo, null, 2)}</pre>
|
||||
<pre>authError: {$authError}</pre>
|
||||
</OidcContext>
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
### Components
|
||||
* OidcContext - component to initiate the OIDC client. You only need one instance in your DOM tree at the root.
|
||||
|
||||
Attributes:
|
||||
* authority - OIDC Authority/issuer/base url for .well-known/openid-configuration
|
||||
* client_id - OAuth ClientId
|
||||
* redirect_uri - default: window.location.href
|
||||
* post_logout_redirect_uri - override the default url that OIDC will redirect to after logout. default: window.location.href
|
||||
|
||||
### Functions
|
||||
* login(preseveRoute = true, callback_url = null) - begin a user login.
|
||||
* logout(logout_url = null) - logout a user.
|
||||
* refreshToken - function to refresh a token.
|
||||
|
||||
### Stores
|
||||
* isLoading - if true OIDC Context is still loading.
|
||||
* isAuthenticated - true if user is currently authenticated
|
||||
* authToken - api token
|
||||
* userInfo - the currently logged in user's info from OIDC
|
||||
* authError - the last authentication error.
|
||||
|
||||
### Constants
|
||||
* OIDC_CONTEXT_CALLBACK_URL,
|
||||
* OIDC_CONTEXT_CLIENT_PROMISE - key for the OIDC client in setContext/getContext.
|
||||
* OIDC_CONTEXT_LOGOUT_URL,
|
||||
|
||||
## Release
|
||||
**use semver**
|
||||
npm publish
|
||||
npm showcase:build
|
||||
npm showcase:publish
|
||||
1
cypress.json
Normal file
1
cypress.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
7
cypress/integration/OidcContext.spec.js
Normal file
7
cypress/integration/OidcContext.spec.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Actions', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:5000');
|
||||
});
|
||||
});
|
||||
18
cypress/plugins/index.js
Normal file
18
cypress/plugins/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
};
|
||||
28
cypress/support/commands.js
Normal file
28
cypress/support/commands.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
// For Cypress Testing Library
|
||||
import '@testing-library/cypress/add-commands';
|
||||
20
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
6432
package-lock.json
generated
Normal file
6432
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
77
package.json
Normal file
77
package.json
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "@dopry/svelte-oidc",
|
||||
"version": "0.0.0",
|
||||
"repository": "https://github.com/dopry/svelte-oidc",
|
||||
"description": "Svelte OIDC Component Library",
|
||||
"keywords": [
|
||||
"svelte"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "dist/index.min.js",
|
||||
"module": "dist/index.min.mjs",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"cy:open": "cypress open",
|
||||
"cy:run": "cypress run",
|
||||
"showcase:publish": "gh-pages -d public",
|
||||
"showcase:build": "rollup -c rollup.config.showcase.js",
|
||||
"showcase:dev": "rollup -c rollup.config.showcase.js -w ",
|
||||
"lint": "eslint --color --ignore-path .gitignore .",
|
||||
"prepublishOnly": "npm run build",
|
||||
"start": "sirv public",
|
||||
"test": "start-server-and-test showcase:dev http://localhost:5000 cy:run"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{html, css, scss, stylus, js, ts, json, yml, md}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
"*.{js, svelte}": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"svelte": "src/components/components.module.js",
|
||||
"dependencies": {
|
||||
"oidc-client": "github:dopry/oidc-client-js#merge-settings"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "7.0.0",
|
||||
"@rollup/plugin-replace": "2.3.0",
|
||||
"@testing-library/cypress": "5.0.2",
|
||||
"autoprefixer": "9.7.3",
|
||||
"cypress": "3.8.2",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-prettier": "6.9.0",
|
||||
"eslint-plugin-cypress": "2.8.1",
|
||||
"eslint-plugin-svelte3": "2.7.3",
|
||||
"gh-pages": "2.2.0",
|
||||
"lint-staged": "9.5.0",
|
||||
"postcss": "7.0.26",
|
||||
"postcss-load-config": "2.1.0",
|
||||
"prettier": "1.19.1",
|
||||
"rollup": "1.29.0",
|
||||
"rollup-plugin-babel": "4.3.3",
|
||||
"rollup-plugin-commonjs": "10.1.0",
|
||||
"rollup-plugin-livereload": "1.0.4",
|
||||
"rollup-plugin-node-resolve": "5.2.0",
|
||||
"rollup-plugin-svelte": "5.1.1",
|
||||
"rollup-plugin-terser": "5.2.0",
|
||||
"sirv-cli": "0.4.5",
|
||||
"start-server-and-test": "1.10.6",
|
||||
"svelte": "3.16.7",
|
||||
"svelte-preprocess": "3.3.0"
|
||||
}
|
||||
}
|
||||
7
public/base.css
Normal file
7
public/base.css
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
18
public/index.html
Normal file
18
public/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link href="favicon.png" rel="icon" type="image/png">
|
||||
<title>svelte-oidc demo</title>
|
||||
<link rel="stylesheet" href="materialize.min.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>@dopry/svelte-oidc demo</h1>
|
||||
<a href="https://github.com/dopry/svelte-oidc" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
||||
<script src="bundle.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
13
public/materialize.min.css
vendored
Normal file
13
public/materialize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
32
rollup.config.js
Normal file
32
rollup.config.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { terser } from 'rollup-plugin-terser';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import pkg from './package.json';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
|
||||
const name = pkg.name
|
||||
.replace(/^(@\S+\/)?(svelte-)?(\S+)/, '$3')
|
||||
.replace(/^\w/, (m) => m.toUpperCase())
|
||||
.replace(/-\w/g, (m) => m[1].toUpperCase());
|
||||
|
||||
export default {
|
||||
input: 'src/components/components.module.js',
|
||||
output: [
|
||||
{ file: pkg.module, format: 'es', sourcemap: true, name },
|
||||
{ file: pkg.main, format: 'umd', sourcemap: true, name }
|
||||
],
|
||||
plugins: [
|
||||
svelte(),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: (importee) =>
|
||||
importee === 'svelte' || importee.startsWith('svelte/'),
|
||||
}),
|
||||
commonjs({
|
||||
include: ['node_modules/**'],
|
||||
}),
|
||||
terser(),
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
66
rollup.config.showcase.js
Normal file
66
rollup.config.showcase.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import replace from '@rollup/plugin-replace';
|
||||
import pkg from './package.json';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
const defaultRedirectUri = production ? 'https://darrelopry.com/svelte-auth0' : 'http://localhost:5000/';
|
||||
const defaultPostLogoutRedirectUri = production ? 'https://darrelopry.com/svelte-auth0' : 'http://localhost:5000/';
|
||||
|
||||
export default {
|
||||
input: 'src/main.js',
|
||||
output: { sourcemap: true, format: 'iife', name: 'app', file: 'public/bundle.js' },
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.OIDC_ISSUER': process.env.OIDC_ISSUER || "https://dev-hvw40i79.auth0.com",
|
||||
'process.env.OIDC_CLIENT_ID': process.env.OIDC_CLIENT_ID || "aOijZt2ug6Ovgzp0HXdF23B6zxwA6PaP",
|
||||
'process.env.OIDC_REDIRECT_URI': process.env.OIDC_REDIRECT_URI || defaultRedirectUri,
|
||||
'process.env.OIDC_POST_LOGOUT_REDIRECT_URI': process.env.OIDC_POST_LOGOUT_REDIRECT_URI || defaultPostLogoutRedirectUri,
|
||||
'pkg.version': pkg.version
|
||||
}),
|
||||
svelte({ dev: true }),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: (importee) =>
|
||||
importee === 'svelte' || importee.startsWith('svelte/'),
|
||||
}),
|
||||
commonjs({
|
||||
include: ['node_modules/**'],
|
||||
}),
|
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
!production && serve(),
|
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('public'),
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
};
|
||||
|
||||
function serve() {
|
||||
let started = false;
|
||||
|
||||
return {
|
||||
writeBundle() {
|
||||
if (!started) {
|
||||
started = true;
|
||||
|
||||
require('child_process').spawn(
|
||||
'npm',
|
||||
['run', 'start', '--', '--dev'],
|
||||
{
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
39
src/App.svelte
Normal file
39
src/App.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<script>
|
||||
import {
|
||||
OidcContext,
|
||||
authError,
|
||||
idToken,
|
||||
accessToken,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
login,
|
||||
logout,
|
||||
userInfo,
|
||||
} from './components/components.module.js';
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<OidcContext
|
||||
issuer="process.env.OIDC_ISSUER"
|
||||
client_id="process.env.OIDC_CLIENT_ID"
|
||||
redirect_uri="process.env.OIDC_REDIRECT_URI"
|
||||
post_logout_redirect_uri="process.env.OIDC_POST_LOGOUT_REDIRECT_URI"
|
||||
>
|
||||
|
||||
<button class="btn" on:click|preventDefault='{() => login() }'>Login</button>
|
||||
<button class="btn" on:click|preventDefault='{() => logout() }'>Logout</button>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th style="width: 20%;">store</th><th style="width: 80%;">value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>isLoading</td><td>{$isLoading}</td></tr>
|
||||
<tr><td>isAuthenticated</td><td>{$isAuthenticated}</td></tr>
|
||||
<tr><td>accessToken</td><td>{$accessToken}</td></tr>
|
||||
<tr><td>idToken</td><td style="word-break: break-all;">{$idToken}</td></tr>
|
||||
<tr><td>userInfo</td><td><pre>{JSON.stringify($userInfo, null, 2)}<pre></td></tr>
|
||||
<tr><td>authError</td><td>{$authError}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</OidcContext>
|
||||
</div>
|
||||
111
src/components/OidcContext.svelte
Normal file
111
src/components/OidcContext.svelte
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<script>
|
||||
import oidcClient from 'oidc-client';
|
||||
const { UserManager } = oidcClient;
|
||||
import { onMount, onDestroy, setContext, getContext } from 'svelte';
|
||||
import {
|
||||
OIDC_CONTEXT_REDIRECT_URI,
|
||||
OIDC_CONTEXT_CLIENT_PROMISE,
|
||||
OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI,
|
||||
idToken,
|
||||
accessToken,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
authError,
|
||||
userInfo
|
||||
} from './oidc';
|
||||
|
||||
// props.
|
||||
export let issuer;
|
||||
export let client_id;
|
||||
export let redirect_uri;
|
||||
export let post_logout_redirect_uri;
|
||||
|
||||
setContext(OIDC_CONTEXT_REDIRECT_URI, redirect_uri);
|
||||
setContext(OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI, post_logout_redirect_uri);
|
||||
|
||||
// getContext doesn't seem to return a value in OnMount, so we'll pass the oidcPromise around by reference.
|
||||
const settings = {
|
||||
authority: issuer,
|
||||
client_id,
|
||||
response_type: 'id_token token',
|
||||
redirect_uri,
|
||||
post_logout_redirect_uri,
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
automaticSilentRenew: true,
|
||||
};
|
||||
|
||||
if (issuer.includes('auth0.com')) {
|
||||
settings.metadata = {
|
||||
// added to overcome missing value in auth0 .well-known/openid-configuration
|
||||
// see: https://github.com/IdentityModel/oidc-client-js/issues/1067
|
||||
// see: https://github.com/IdentityModel/oidc-client-js/pull/1068
|
||||
end_session_endpoint: `process.env.OIDC_ISSUER/v2/logout?client_id=process.env.OIDC_CLIENT_ID`,
|
||||
};
|
||||
}
|
||||
const userManager = new UserManager(settings);
|
||||
userManager.events.addUserLoaded(function () {
|
||||
const user = userManager.getUser();
|
||||
accessToken.set(user.access_token);
|
||||
idToken.set(user.id_token);
|
||||
userInfo.set(user.profile);
|
||||
});
|
||||
|
||||
userManager.events.addUserUnloaded(function () {
|
||||
idToken.set('');
|
||||
accessToken.set('');
|
||||
});
|
||||
|
||||
userManager.events.addSilentRenewError(function (e) {
|
||||
authError.set(`silentRenewError: ${e.message}`);
|
||||
});
|
||||
|
||||
let oidcPromise = Promise.resolve(userManager);
|
||||
|
||||
setContext(OIDC_CONTEXT_CLIENT_PROMISE, oidcPromise);
|
||||
|
||||
|
||||
async function handleOnMount() {
|
||||
// on run onMount after oidc
|
||||
const oidc = await oidcPromise;
|
||||
|
||||
// Not all browsers support this, please program defensively!
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Check if something went wrong during login redirect
|
||||
// and extract the error message
|
||||
if (params.has('error')) {
|
||||
authError.set(new Error(params.get('error_description')));
|
||||
}
|
||||
|
||||
// if code then login success
|
||||
if (params.has('code')) {
|
||||
// handle the redirect response.
|
||||
const response = await oidc.signinRedirectCallback();
|
||||
let state = (response && response.state) || {}
|
||||
// Can be smart here and redirect to original path instead of root
|
||||
const url = state && state.targetUrl ? state.targetUrl : window.location.pathname;
|
||||
state = { ...state, isRedirectCallback: true };
|
||||
|
||||
// redirect to the last page we were on when login was configured if it was passed.
|
||||
history.replaceState(state, "", url);
|
||||
// location.href = url;
|
||||
// clear errors on login.
|
||||
authError.set(null);
|
||||
}
|
||||
|
||||
const user = await oidc.getUser();
|
||||
isAuthenticated.set(!!user);
|
||||
accessToken.set(user.access_token);
|
||||
idToken.set(user.id_token);
|
||||
userInfo.set(user.profile);
|
||||
isLoading.set(false);
|
||||
}
|
||||
async function handleOnDestroy() {}
|
||||
|
||||
onMount(handleOnMount);
|
||||
onDestroy(handleOnDestroy);
|
||||
</script>
|
||||
|
||||
|
||||
<slot></slot>
|
||||
3
src/components/components.module.js
Normal file
3
src/components/components.module.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './oidc'
|
||||
export { default as OidcContext } from './OidcContext.svelte';
|
||||
|
||||
62
src/components/oidc.js
Normal file
62
src/components/oidc.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
/**
|
||||
* Stores
|
||||
*/
|
||||
export const isLoading = writable(true);
|
||||
export const isAuthenticated = writable(false);
|
||||
export const accessToken = writable('');
|
||||
export const idToken = writable('');
|
||||
export const userInfo = writable({});
|
||||
export const authError = writable(null);
|
||||
|
||||
/**
|
||||
* Context Keys
|
||||
*
|
||||
* using an object literal means the keys are guaranteed not to conflict in any circumstance (since an object only has
|
||||
* referential equality to itself, i.e. {} !== {} whereas "x" === "x"), even when you have multiple different contexts
|
||||
* operating across many component layers.
|
||||
*/
|
||||
export const OIDC_CONTEXT_CLIENT_PROMISE = {};
|
||||
export const OIDC_CONTEXT_REDIRECT_URI = {};
|
||||
export const OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI = {};
|
||||
|
||||
/**
|
||||
* Refresh the accessToken store.
|
||||
*/
|
||||
export async function refreshToken() {
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const token = await oidc.signinSilent();
|
||||
accessToken.set(token.accessToken);
|
||||
idToken.set(token.idToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate Register/Login flow.
|
||||
*
|
||||
* @param {boolean} preserveRoute - store current location so callback handler will navigate back to it.
|
||||
* @param {string} callback_url - explicit path to use for the callback.
|
||||
*/
|
||||
export async function login(preserveRoute = true, callback_url = null) {
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const redirect_uri = callback_url || getContext(OIDC_CONTEXT_REDIRECT_URI) || window.location.href;
|
||||
|
||||
// try to keep the user on the same page from which they triggered login. If set to false should typically
|
||||
// cause redirect to /.
|
||||
const appState = (preserveRoute) ? { pathname: window.location.pathname, search: window.location.search } : {}
|
||||
await oidc.signinRedirect({ redirect_uri, appState });
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out the current user.
|
||||
*
|
||||
* @param {string} logout_url - specify the url to return to after login.
|
||||
*/
|
||||
export async function logout(logout_url = null) {
|
||||
// getContext(OIDC_CONTEXT_CLIENT_PROMISE) returns a promise.
|
||||
const oidc = await getContext(OIDC_CONTEXT_CLIENT_PROMISE)
|
||||
const returnTo = logout_url || getContext(OIDC_CONTEXT_POST_LOGOUT_REDIRECT_URI) || window.location.href;
|
||||
accessToken.set('');
|
||||
oidc.signoutRedirect({ returnTo });
|
||||
}
|
||||
8
src/main.js
Normal file
8
src/main.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import App from './App.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.body,
|
||||
props: {},
|
||||
});
|
||||
|
||||
export default app;
|
||||
Loading…
Add table
Add a link
Reference in a new issue