├── .gitignore
├── LICENSE
├── README.md
├── appveyor.yml
├── docs
├── Images
│ ├── Skoruba-Logo-ReadMe.png
│ └── react-oidc-client-screenshot.png
└── README.md
└── src
├── .gitignore
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
├── oidc-client.min.js
├── signin-callback.html
└── silent-renew.html
├── src
├── components
│ ├── AppContent.tsx
│ ├── AuthContent.tsx
│ ├── Buttons.test.tsx
│ ├── Buttons.tsx
│ ├── Header.test.tsx
│ ├── Header.tsx
│ ├── JsonTreeViewer.tsx
│ └── __snapshots__
│ │ ├── Buttons.test.tsx.snap
│ │ └── Header.test.tsx.snap
├── containers
│ ├── App.css
│ ├── App.test.tsx
│ └── App.tsx
├── helpers
│ └── Constants.ts
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── registerServiceWorker.ts
├── services
│ ├── ApiService.ts
│ └── AuthService.ts
└── setupTests.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jan Škoruba
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # React-oidc-client-js
4 |
5 | > OpenID Connect (OIDC) client with React and typescript
6 |
7 | - This is sample application that contains [oidc-client-js](https://github.com/IdentityModel/oidc-client-js) and `React` with `Typescript`.
8 |
9 | - The application is based on `create-react-app` - [Create React App](https://github.com/facebook/create-react-app)
10 |
11 | # Project status
12 | [](https://ci.appveyor.com/project/JanSkoruba/react-oidc-client-js)
13 |
14 | # Installation
15 |
16 | ## Cloning app
17 |
18 | - `git clone https://github.com/skoruba/react-oidc-client-js.git`
19 | - `cd src/`
20 |
21 | ## Install dependecies
22 |
23 | - Install dependecies - `yarn install`
24 |
25 | ## Running app
26 |
27 | - `yarn start` - start the web server that is running on [http://localhost:4200](http://localhost:4200)
28 |
29 | - The application is connected to `OpenID Connect Provider` that is running on [https://demo.identityserver.io/](https://demo.identityserver.io/)
30 | - This STS has configured a SPA client to run on http://localhost:4200, therefore a sample application will be running on this port `4200`.
31 |
32 | ## App preview
33 |
34 | 
35 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '12'
4 | - nodejs_version: '10'
5 | - nodejs_version: '8'
6 | install:
7 | - ps: Install-Product node $env:nodejs_version
8 | - npm install --global npm@latest
9 | - set PATH=%APPDATA%\npm;%PATH%
10 | - npm install -g create-react-app
11 | - npm install
12 | matrix:
13 | fast_finish: true
14 | build: off
15 | shallow_clone: true
16 | test_script:
17 | - node --version
18 | - npm --version
19 | - cd src/
20 | - npm install -g create-react-app
21 | - npm install
22 | - npm test
23 | cache:
24 | - '%APPDATA%\npm-cache'
25 |
--------------------------------------------------------------------------------
/docs/Images/Skoruba-Logo-ReadMe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skoruba/react-oidc-client-js/76fd43b46c565ec7b8869d1e97879cf737aba572/docs/Images/Skoruba-Logo-ReadMe.png
--------------------------------------------------------------------------------
/docs/Images/react-oidc-client-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skoruba/react-oidc-client-js/76fd43b46c565ec7b8869d1e97879cf737aba572/docs/Images/react-oidc-client-screenshot.png
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # react-oidc-client-js docs
4 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-oidc-client-js",
3 | "description": "OpenID Connect (OIDC) javascript client with React",
4 | "version": "3.0.0",
5 | "dependencies": {
6 | "axios": "0.19.2",
7 | "core-js": "3.6.5",
8 | "bootstrap": "4.5.0",
9 | "enzyme": "3.11.0",
10 | "enzyme-adapter-react-16": "1.15.2",
11 | "oidc-client": "1.10.1",
12 | "ramda": "0.27.0",
13 | "react": "16.13.1",
14 | "react-dom": "16.13.1",
15 | "react-json-tree": "0.11.2",
16 | "react-scripts": "3.4.1",
17 | "react-test-renderer": "16.13.1",
18 | "react-app-polyfill": "1.0.6",
19 | "react-toastify": "6.0.8"
20 | },
21 | "scripts": {
22 | "start": "set PORT=4200 && react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test --env=jsdom",
25 | "eject": "react-scripts eject"
26 | },
27 | "devDependencies": {
28 | "@types/axios": "0.14.0",
29 | "@types/enzyme": "3.10.5",
30 | "@types/enzyme-adapter-react-16": "1.0.6",
31 | "@types/jest": "26.0.4",
32 | "@types/node": "14.0.23",
33 | "@types/ramda": "0.27.11",
34 | "@types/react": "16.9.43",
35 | "@types/react-dom": "16.9.8",
36 | "@types/react-json-tree": "0.6.11",
37 | "@types/react-test-renderer": "16.9.2",
38 | "@types/toastr": "2.1.38",
39 | "typescript": "3.9.6"
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version",
51 | "ie 11"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skoruba/react-oidc-client-js/76fd43b46c565ec7b8869d1e97879cf737aba572/src/public/favicon.ico
--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/public/signin-callback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Authentification callback processing..
6 |
7 |
8 |
9 |
12 |
13 | Authentification callback processing...
14 |
15 |
16 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/public/silent-renew.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/src/components/AppContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { ToastContainer, toast } from 'react-toastify';
4 | import { ApiService } from '../services/ApiService';
5 | import { AuthService } from '../services/AuthService';
6 |
7 | import AuthContent from './AuthContent';
8 | import Buttons from './Buttons';
9 |
10 | export default class AppContent extends React.Component {
11 | public authService: AuthService;
12 | public apiService: ApiService;
13 | private shouldCancel: boolean;
14 |
15 | constructor(props: any) {
16 | super(props);
17 |
18 | this.authService = new AuthService();
19 | this.apiService = new ApiService();
20 | this.state = { user: {}, api: {} };
21 | this.shouldCancel = false;
22 | }
23 |
24 | public componentDidMount() {
25 | this.getUser();
26 | }
27 |
28 | public login = () => {
29 | this.authService.login();
30 | };
31 |
32 | public callApi = () => {
33 | this.apiService
34 | .callApi()
35 | .then(data => {
36 | this.setState({ api: data.data });
37 | toast.success('Api return successfully data, check in section - Api response');
38 | })
39 | .catch(error => {
40 | toast.error(error);
41 | });
42 | };
43 |
44 | public componentWillUnmount() {
45 | this.shouldCancel = true;
46 | }
47 |
48 | public renewToken = () => {
49 | this.authService
50 | .renewToken()
51 | .then(user => {
52 | toast.success('Token has been sucessfully renewed. :-)');
53 | this.getUser();
54 | })
55 | .catch(error => {
56 | toast.error(error);
57 | });
58 | };
59 |
60 | public logout = () => {
61 | this.authService.logout();
62 | };
63 |
64 | public getUser = () => {
65 | this.authService.getUser().then(user => {
66 | if (user) {
67 | toast.success('User has been successfully loaded from store.');
68 | } else {
69 | toast.info('You are not logged in.');
70 | }
71 |
72 | if (!this.shouldCancel) {
73 | this.setState({ user });
74 | }
75 | });
76 | };
77 |
78 | public render() {
79 | return (
80 | <>
81 |
82 |
83 |
90 |
91 |
92 | >
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/src/components/AuthContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import JsonTreeViewer from './JsonTreeViewer';
3 |
4 | export interface IAuthContentProps {
5 | api: any;
6 | user: any;
7 | }
8 |
9 | export default class AuthContent extends React.Component {
10 | public shouldExpandNode = (keyPath: Array, data: [any] | {}, level: number) => {
11 | return true;
12 | };
13 |
14 | public render() {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/src/components/Buttons.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import * as React from 'react';
3 | import * as renderer from 'react-test-renderer';
4 |
5 | import Buttons from './Buttons';
6 |
7 | describe(Buttons, () => {
8 | const mockLogin = jest.fn();
9 | const mockLogout = jest.fn();
10 | const mockRenewToken = jest.fn();
11 | const mockCallApi = jest.fn();
12 | const mockGetUser = jest.fn();
13 |
14 | const component = shallow(
15 |
22 | );
23 |
24 | it('renders and matches our snapshot', () => {
25 | const buttonComponent = renderer.create(
26 |
33 | );
34 | const tree = buttonComponent.toJSON();
35 | expect(tree).toMatchSnapshot();
36 | });
37 |
38 | it('contains the image and h1', () => {
39 | expect(component.find('button')).toHaveLength(5);
40 | });
41 |
42 | it('try to click on login button', () => {
43 | component.find('button.btn-login').simulate('click');
44 | expect(mockLogin).toBeCalled();
45 | });
46 |
47 | it('try to click on logout button', () => {
48 | component.find('button.btn-logout').simulate('click');
49 | expect(mockLogout).toBeCalled();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/src/components/Buttons.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IButtonsProps {
4 | login: () => void;
5 | getUser: () => void;
6 | callApi: () => void;
7 | renewToken: () => void;
8 | logout: () => void;
9 | }
10 |
11 | const Buttons: React.SFC = props => {
12 | return (
13 |
14 |
15 |
18 |
21 |
24 |
27 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default Buttons;
36 |
--------------------------------------------------------------------------------
/src/src/components/Header.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import * as React from 'react';
3 | import * as renderer from 'react-test-renderer';
4 |
5 | import logo from '../logo.svg';
6 | import Header from './Header';
7 |
8 | describe(Header, () => {
9 | const pageTitle = 'React app with oidc-client-js';
10 |
11 | const component = shallow();
12 |
13 | it('renders and matches our snapshot', () => {
14 | const headerComponent = renderer.create();
15 | const tree = headerComponent.toJSON();
16 | expect(tree).toMatchSnapshot();
17 | });
18 |
19 | it('contains the image and h1', () => {
20 | expect(component.find('img')).toHaveLength(1);
21 | expect(component.find('h1')).toHaveLength(1);
22 | });
23 |
24 | it('h1 contains correct title', () => {
25 | expect(component.find('h1').text()).toEqual(pageTitle);
26 | expect(component.contains(pageTitle)).toEqual(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export interface IHeaderProps {
4 | pageTitle: string;
5 | logoSrc: any;
6 | }
7 |
8 | export default function Header(props: IHeaderProps) {
9 | return (
10 |
11 |
12 | {props.pageTitle}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/src/components/JsonTreeViewer.tsx:
--------------------------------------------------------------------------------
1 | import * as R from 'ramda';
2 | import * as React from 'react';
3 | import JSONTree from 'react-json-tree';
4 |
5 | export interface IJsonTreeViewer {
6 | data: any;
7 | title: string;
8 | shouldExpandNode?: (keyPath: Array, data: [any] | {}, level: number) => boolean;
9 | }
10 |
11 | export default class JsonTreeViewer extends React.Component {
12 | public renderJsonData() {
13 | return R.not(R.isEmpty(this.props.data)) && R.not(R.isNil(this.props.data)) ? (
14 | <>
15 | {this.props.title}
16 |
17 | >
18 | ) : null;
19 | }
20 |
21 | public render() {
22 | return this.renderJsonData();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/src/components/__snapshots__/Buttons.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Buttons renders and matches our snapshot 1`] = `
4 |
7 |
15 |
26 |
37 |
48 |
59 |
70 |
71 |
72 | `;
73 |
--------------------------------------------------------------------------------
/src/src/components/__snapshots__/Header.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Header renders and matches our snapshot 1`] = `
4 |
7 |
12 |
15 | React app with oidc-client-js
16 |
17 |
18 | `;
19 |
--------------------------------------------------------------------------------
/src/src/containers/App.css:
--------------------------------------------------------------------------------
1 | .App-logo {
2 | animation: App-logo-spin infinite 20s linear;
3 | height: 80px;
4 | }
5 |
6 | .App-header {
7 | background-color: #222;
8 | height: 150px;
9 | padding: 20px;
10 | color: white;
11 | text-align: center;
12 | }
13 |
14 | .App-title {
15 | font-size: 1.5em;
16 | }
17 |
18 | .App-intro {
19 | font-size: large;
20 | }
21 |
22 | @keyframes App-logo-spin {
23 | from {
24 | transform: rotate(0deg);
25 | }
26 | to {
27 | transform: rotate(360deg);
28 | }
29 | }
30 |
31 | #toast-container > div {
32 | opacity: 1;
33 | }
34 |
--------------------------------------------------------------------------------
/src/src/containers/App.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | const storageMock = {
6 | clear: jest.fn(),
7 | getItem: jest.fn(),
8 | setItem: jest.fn()
9 | };
10 |
11 | (global as any).sessionStorage = storageMock;
12 | (global as any).localStorage = storageMock;
13 |
14 | it('renders without crashing', () => {
15 | const div = document.createElement('div');
16 | ReactDOM.render(, div);
17 | ReactDOM.unmountComponentAtNode(div);
18 | });
19 |
--------------------------------------------------------------------------------
/src/src/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import AppContent from '../components/AppContent';
3 | import Header from '../components/Header';
4 | import logo from '../logo.svg';
5 | import './App.css';
6 |
7 | class App extends React.Component {
8 | public render() {
9 | return (
10 |
20 | );
21 | }
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/src/helpers/Constants.ts:
--------------------------------------------------------------------------------
1 | export class Constants {
2 | public static stsAuthority = 'https://demo.identityserver.io/';
3 | public static clientId = 'interactive.public';
4 | public static clientRoot = 'http://localhost:4200/';
5 | public static clientScope = 'openid profile email api';
6 |
7 | public static apiRoot = 'https://demo.identityserver.io/api/';
8 | }
9 |
--------------------------------------------------------------------------------
/src/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/src/index.tsx:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.min.css';
2 | import 'react-toastify/dist/ReactToastify.css';
3 |
4 | import 'react-app-polyfill/ie11';
5 | import 'core-js';
6 |
7 | import * as React from 'react';
8 | import * as ReactDOM from 'react-dom';
9 | import App from './containers/App';
10 | import './index.css';
11 | import registerServiceWorker from './registerServiceWorker';
12 |
13 | ReactDOM.render(, document.getElementById('root') as HTMLElement);
14 | registerServiceWorker();
15 |
--------------------------------------------------------------------------------
/src/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:no-console
2 | // In production, we register a service worker to serve assets from local cache.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on the 'N+1' visit to a page, since previously
7 | // cached resources are updated in the background.
8 |
9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
10 | // This link also includes instructions on opting out of this behavior.
11 |
12 | const isLocalhost = Boolean(
13 | window.location.hostname === 'localhost' ||
14 | // [::1] is the IPv6 localhost address.
15 | window.location.hostname === '[::1]' ||
16 | // 127.0.0.1/8 is considered localhost for IPv4.
17 | window.location.hostname.match(
18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
19 | )
20 | );
21 |
22 | export default function register() {
23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
24 | // The URL constructor is available in all browsers that support SW.
25 | const publicUrl = new URL(
26 | process.env.PUBLIC_URL!,
27 | window.location.toString()
28 | );
29 | if (publicUrl.origin !== window.location.origin) {
30 | // Our service worker won't work if PUBLIC_URL is on a different origin
31 | // from what our page is served on. This might happen if a CDN is used to
32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
33 | return;
34 | }
35 |
36 | window.addEventListener('load', () => {
37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
38 |
39 | if (isLocalhost) {
40 | // This is running on localhost. Lets check if a service worker still exists or not.
41 | checkValidServiceWorker(swUrl);
42 |
43 | // Add some additional logging to localhost, pointing developers to the
44 | // service worker/PWA documentation.
45 | navigator.serviceWorker.ready.then(() => {
46 | console.log(
47 | 'This web app is being served cache-first by a service ' +
48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
49 | );
50 | });
51 | } else {
52 | // Is not local host. Just register service worker
53 | registerValidSW(swUrl);
54 | }
55 | });
56 | }
57 | }
58 |
59 | function registerValidSW(swUrl: string) {
60 | navigator.serviceWorker
61 | .register(swUrl)
62 | .then(registration => {
63 | registration.onupdatefound = () => {
64 | const installingWorker = registration.installing;
65 | if (installingWorker) {
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the old content will have been purged and
70 | // the fresh content will have been added to the cache.
71 | // It's the perfect time to display a 'New content is
72 | // available; please refresh.' message in your web app.
73 | console.log('New content is available; please refresh.');
74 | } else {
75 | // At this point, everything has been precached.
76 | // It's the perfect time to display a
77 | // 'Content is cached for offline use.' message.
78 | console.log('Content is cached for offline use.');
79 | }
80 | }
81 | };
82 | }
83 | };
84 | })
85 | .catch(error => {
86 | console.error('Error during service worker registration:', error);
87 | });
88 | }
89 |
90 | function checkValidServiceWorker(swUrl: string) {
91 | // Check if the service worker can be found. If it can't reload the page.
92 | fetch(swUrl)
93 | .then(response => {
94 | // Ensure service worker exists, and that we really are getting a JS file.
95 | if (
96 | response.status === 404 ||
97 | response.headers.get('content-type')!.indexOf('javascript') === -1
98 | ) {
99 | // No service worker found. Probably a different app. Reload the page.
100 | navigator.serviceWorker.ready.then(registration => {
101 | registration.unregister().then(() => {
102 | window.location.reload();
103 | });
104 | });
105 | } else {
106 | // Service worker found. Proceed as normal.
107 | registerValidSW(swUrl);
108 | }
109 | })
110 | .catch(() => {
111 | console.log(
112 | 'No internet connection found. App is running in offline mode.'
113 | );
114 | });
115 | }
116 |
117 | export function unregister() {
118 | if ('serviceWorker' in navigator) {
119 | navigator.serviceWorker.ready.then(registration => {
120 | registration.unregister();
121 | });
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/src/services/ApiService.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { Constants } from '../helpers/Constants';
3 | import { AuthService } from './AuthService';
4 |
5 | export class ApiService {
6 | private authService: AuthService;
7 |
8 | constructor() {
9 | this.authService = new AuthService();
10 | }
11 |
12 | public callApi(): Promise {
13 | return this.authService.getUser().then(user => {
14 | if (user && user.access_token) {
15 | return this._callApi(user.access_token).catch(error => {
16 | if (error.response.status === 401) {
17 | return this.authService.renewToken().then(renewedUser => {
18 | return this._callApi(renewedUser.access_token);
19 | });
20 | }
21 | throw error;
22 | });
23 | } else if (user) {
24 | return this.authService.renewToken().then(renewedUser => {
25 | return this._callApi(renewedUser.access_token);
26 | });
27 | } else {
28 | throw new Error('user is not logged in');
29 | }
30 | });
31 | }
32 |
33 | private _callApi(token: string) {
34 | const headers = {
35 | Accept: 'application/json',
36 | Authorization: 'Bearer ' + token
37 | };
38 |
39 | return axios.get(Constants.apiRoot + 'test', { headers });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/src/services/AuthService.ts:
--------------------------------------------------------------------------------
1 | import { Log, User, UserManager } from 'oidc-client';
2 |
3 | import { Constants } from '../helpers/Constants';
4 |
5 | export class AuthService {
6 | public userManager: UserManager;
7 |
8 | constructor() {
9 | const settings = {
10 | authority: Constants.stsAuthority,
11 | client_id: Constants.clientId,
12 | redirect_uri: `${Constants.clientRoot}signin-callback.html`,
13 | silent_redirect_uri: `${Constants.clientRoot}silent-renew.html`,
14 | // tslint:disable-next-line:object-literal-sort-keys
15 | post_logout_redirect_uri: `${Constants.clientRoot}`,
16 | response_type: 'code',
17 | scope: Constants.clientScope
18 | };
19 | this.userManager = new UserManager(settings);
20 |
21 | Log.logger = console;
22 | Log.level = Log.INFO;
23 | }
24 |
25 | public getUser(): Promise {
26 | return this.userManager.getUser();
27 | }
28 |
29 | public login(): Promise {
30 | return this.userManager.signinRedirect();
31 | }
32 |
33 | public renewToken(): Promise {
34 | return this.userManager.signinSilent();
35 | }
36 |
37 | public logout(): Promise {
38 | return this.userManager.signoutRedirect();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "preserve"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "linterOptions": {
4 | "exclude": [
5 | "config/**/*.js",
6 | "node_modules/**/*.ts"
7 | ]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------