From 6447462eab2f69ef1a737488cd78673dfb8d4271 Mon Sep 17 00:00:00 2001 From: Zoltan Takacs Date: Thu, 22 Apr 2021 14:00:42 +0200 Subject: [PATCH 1/3] Login from code --- apps/sensenet/cypress.json | 2 +- apps/sensenet/cypress/support/commands.js | 37 ++++--------- examples/sn-react-browser/.env | 2 + .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/login-form.tsx | 23 -------- .../src/components/navbar.tsx | 7 +-- .../sn-react-browser/src/configuration.ts | 18 +++---- examples/sn-react-calendar/.env | 2 + .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/login-form.tsx | 23 -------- .../src/components/navbar.tsx | 7 +-- .../sn-react-calendar/src/configuration.ts | 18 +++---- examples/sn-react-imagegallery/.env | 2 + .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/login-form.tsx | 23 -------- .../src/configuration.ts | 16 +++--- examples/sn-react-memoapp/.env | 2 + .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/login-form.tsx | 23 -------- .../src/components/navbar.tsx | 7 +-- .../sn-react-memoapp/src/configuration.ts | 18 +++---- examples/sn-react-tasklist/.env | 2 + .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/login-form.tsx | 23 -------- .../sn-react-tasklist/src/configuration.ts | 18 +++---- .../tsconfig.json | 14 +++-- examples/sn-react-usersearch/.env | 2 + examples/sn-react-usersearch/src/app.tsx | 2 - .../src/components/app-providers.tsx | 37 ++++++------- .../src/components/header.tsx | 16 +----- .../src/components/login-form.tsx | 23 -------- .../src/components/user-search.tsx | 2 +- .../sn-react-usersearch/src/configuration.ts | 18 +++---- examples/sn-react-usersearch/tsconfig.json | 14 +++-- .../src/code-login.ts | 52 +++++++++++++++++++ .../sn-authentication-oidc-react/src/index.ts | 1 + 36 files changed, 239 insertions(+), 400 deletions(-) delete mode 100644 examples/sn-react-browser/src/components/login-form.tsx delete mode 100644 examples/sn-react-calendar/src/components/login-form.tsx delete mode 100644 examples/sn-react-imagegallery/src/components/login-form.tsx delete mode 100644 examples/sn-react-memoapp/src/components/login-form.tsx delete mode 100644 examples/sn-react-tasklist/src/components/login-form.tsx delete mode 100644 examples/sn-react-usersearch/src/components/login-form.tsx create mode 100644 packages/sn-authentication-oidc-react/src/code-login.ts diff --git a/apps/sensenet/cypress.json b/apps/sensenet/cypress.json index 36b1d1e5c..3ece198d7 100644 --- a/apps/sensenet/cypress.json +++ b/apps/sensenet/cypress.json @@ -7,7 +7,7 @@ "identityServer": "https://is.demo.sensenet.com", "users": { "admin": { - "clientId": "businesscat", + "clientId": "KxkgOTAxLLCicUx7", "clientSecret": "", "id": "/Root/IMS/Public('businesscat')" }, diff --git a/apps/sensenet/cypress/support/commands.js b/apps/sensenet/cypress/support/commands.js index fcd4f0a2f..9cee0005b 100644 --- a/apps/sensenet/cypress/support/commands.js +++ b/apps/sensenet/cypress/support/commands.js @@ -1,4 +1,6 @@ import 'cypress-file-upload' +import { codeLogin } from '@sensenet/authentication-oidc-react' + // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite @@ -27,34 +29,13 @@ import 'cypress-file-upload' Cypress.Commands.add('login', (userType = 'admin') => { const user = Cypress.env('users')[userType] - - const configuration = { - client_id: user.clientId, - client_secret: Cypress.env(`secret_${userType}`) || user.clientSecret, - grant_type: 'client_credentials', - scope: encodeURIComponent('sensenet'), - } - - const requestBody = Object.keys(configuration).reduce((acc, current, idx) => { - return `${acc}${current}=${configuration[current]}${idx === Object.keys(configuration).length - 1 ? '' : '&'}` - }, '') - - cy.request({ - url: `${Cypress.env('identityServer')}/connect/token`, - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: requestBody, - }).then((resp) => { - const oidcUser = resp.body - - oidcUser.profile = { - sub: user.id, - } - - window.sessionStorage.setItem( - `oidc.user:${Cypress.env('identityServer')}:11V28Add7IaP1iFw`, - JSON.stringify(oidcUser), - ) + codeLogin({ + clientId: user.clientId, + clientSecret: Cypress.env(`secret_${userType}`) || user.clientSecret, + identityServerUrl: Cypress.env('identityServer'), + appId: '11V28Add7IaP1iFw', + userId: user.id, + fetchMethod: (url, options) => cy.request({ url, ...options }), }) }) diff --git a/examples/sn-react-browser/.env b/examples/sn-react-browser/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-browser/.env +++ b/examples/sn-react-browser/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-browser/src/components/app-providers.tsx b/examples/sn-react-browser/src/components/app-providers.tsx index e0415c375..d87b30ddb 100644 --- a/examples/sn-react-browser/src/components/app-providers.tsx +++ b/examples/sn-react-browser/src/components/app-providers.tsx @@ -1,40 +1,35 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-browser/src/components/login-form.tsx b/examples/sn-react-browser/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-browser/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-browser/src/components/navbar.tsx b/examples/sn-react-browser/src/components/navbar.tsx index aaa234d00..87fc16402 100644 --- a/examples/sn-react-browser/src/components/navbar.tsx +++ b/examples/sn-react-browser/src/components/navbar.tsx @@ -1,5 +1,4 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { AppBar, Button, Toolbar, Typography } from '@material-ui/core' +import { AppBar, Toolbar, Typography } from '@material-ui/core' import { createStyles, makeStyles } from '@material-ui/core/styles' import React from 'react' @@ -17,7 +16,6 @@ const useStyles = makeStyles(() => * Navbar component */ export const NavBarComponent: React.FunctionComponent = () => { - const { logout } = useOidcAuthentication() const classes = useStyles() return ( @@ -27,9 +25,6 @@ export const NavBarComponent: React.FunctionComponent = () => { Document Browser - diff --git a/examples/sn-react-browser/src/configuration.ts b/examples/sn-react-browser/src/configuration.ts index 62e7aea02..1ad42a02d 100644 --- a/examples/sn-react-browser/src/configuration.ts +++ b/examples/sn-react-browser/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' -export const repositoryUrl = 'https://dev.demo.sensenet.com/' +export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-calendar/.env b/examples/sn-react-calendar/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-calendar/.env +++ b/examples/sn-react-calendar/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-calendar/src/components/app-providers.tsx b/examples/sn-react-calendar/src/components/app-providers.tsx index e0415c375..d87b30ddb 100644 --- a/examples/sn-react-calendar/src/components/app-providers.tsx +++ b/examples/sn-react-calendar/src/components/app-providers.tsx @@ -1,40 +1,35 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-calendar/src/components/login-form.tsx b/examples/sn-react-calendar/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-calendar/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-calendar/src/components/navbar.tsx b/examples/sn-react-calendar/src/components/navbar.tsx index 7cd9b3a51..e7db0a12a 100644 --- a/examples/sn-react-calendar/src/components/navbar.tsx +++ b/examples/sn-react-calendar/src/components/navbar.tsx @@ -1,5 +1,4 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { AppBar, Button, Toolbar, Typography } from '@material-ui/core' +import { AppBar, Toolbar, Typography } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import React from 'react' @@ -17,7 +16,6 @@ const useStyles = makeStyles((theme: Theme) => * Navbar component */ export const NavBarComponent: React.FunctionComponent = () => { - const { logout } = useOidcAuthentication() const classes = useStyles() return ( @@ -27,9 +25,6 @@ export const NavBarComponent: React.FunctionComponent = () => { Calendar - diff --git a/examples/sn-react-calendar/src/configuration.ts b/examples/sn-react-calendar/src/configuration.ts index 62e7aea02..1ad42a02d 100644 --- a/examples/sn-react-calendar/src/configuration.ts +++ b/examples/sn-react-calendar/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' -export const repositoryUrl = 'https://dev.demo.sensenet.com/' +export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-imagegallery/.env b/examples/sn-react-imagegallery/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-imagegallery/.env +++ b/examples/sn-react-imagegallery/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-imagegallery/src/components/app-providers.tsx b/examples/sn-react-imagegallery/src/components/app-providers.tsx index 6d9b29958..0b9cff4e1 100644 --- a/examples/sn-react-imagegallery/src/components/app-providers.tsx +++ b/examples/sn-react-imagegallery/src/components/app-providers.tsx @@ -1,44 +1,39 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' import { MuiThemeProvider } from '@material-ui/core' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' import { theme } from '../theme' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-imagegallery/src/components/login-form.tsx b/examples/sn-react-imagegallery/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-imagegallery/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-imagegallery/src/configuration.ts b/examples/sn-react-imagegallery/src/configuration.ts index 337c08573..1ad42a02d 100644 --- a/examples/sn-react-imagegallery/src/configuration.ts +++ b/examples/sn-react-imagegallery/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-memoapp/.env b/examples/sn-react-memoapp/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-memoapp/.env +++ b/examples/sn-react-memoapp/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-memoapp/src/components/app-providers.tsx b/examples/sn-react-memoapp/src/components/app-providers.tsx index e0415c375..d87b30ddb 100644 --- a/examples/sn-react-memoapp/src/components/app-providers.tsx +++ b/examples/sn-react-memoapp/src/components/app-providers.tsx @@ -1,40 +1,35 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-memoapp/src/components/login-form.tsx b/examples/sn-react-memoapp/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-memoapp/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-memoapp/src/components/navbar.tsx b/examples/sn-react-memoapp/src/components/navbar.tsx index a49340112..232f08a0b 100644 --- a/examples/sn-react-memoapp/src/components/navbar.tsx +++ b/examples/sn-react-memoapp/src/components/navbar.tsx @@ -1,5 +1,4 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { AppBar, Button, Toolbar, Typography } from '@material-ui/core' +import { AppBar, Toolbar, Typography } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import React from 'react' @@ -18,7 +17,6 @@ const useStyles = makeStyles((theme: Theme) => * Navbar component */ export const NavBarComponent: React.FunctionComponent = () => { - const { logout } = useOidcAuthentication() const classes = useStyles() return ( @@ -28,9 +26,6 @@ export const NavBarComponent: React.FunctionComponent = () => { Memo application - diff --git a/examples/sn-react-memoapp/src/configuration.ts b/examples/sn-react-memoapp/src/configuration.ts index 62e7aea02..1ad42a02d 100644 --- a/examples/sn-react-memoapp/src/configuration.ts +++ b/examples/sn-react-memoapp/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' -export const repositoryUrl = 'https://dev.demo.sensenet.com/' +export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-tasklist/.env b/examples/sn-react-tasklist/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-tasklist/.env +++ b/examples/sn-react-tasklist/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-tasklist/src/components/app-providers.tsx b/examples/sn-react-tasklist/src/components/app-providers.tsx index e0415c375..d87b30ddb 100644 --- a/examples/sn-react-tasklist/src/components/app-providers.tsx +++ b/examples/sn-react-tasklist/src/components/app-providers.tsx @@ -1,40 +1,35 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-tasklist/src/components/login-form.tsx b/examples/sn-react-tasklist/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-tasklist/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-tasklist/src/configuration.ts b/examples/sn-react-tasklist/src/configuration.ts index 62e7aea02..1ad42a02d 100644 --- a/examples/sn-react-tasklist/src/configuration.ts +++ b/examples/sn-react-tasklist/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' -export const repositoryUrl = 'https://dev.demo.sensenet.com/' +export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-typescript-boilerplate/tsconfig.json b/examples/sn-react-typescript-boilerplate/tsconfig.json index 3a301d5c9..663219d18 100644 --- a/examples/sn-react-typescript-boilerplate/tsconfig.json +++ b/examples/sn-react-typescript-boilerplate/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -13,7 +17,11 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react" + "jsx": "react-jsx", + "noFallthroughCasesInSwitch": true }, - "include": ["src", "../../typings"] + "include": [ + "src", + "../../typings" + ] } diff --git a/examples/sn-react-usersearch/.env b/examples/sn-react-usersearch/.env index 6f809cc25..f165b2ecb 100644 --- a/examples/sn-react-usersearch/.env +++ b/examples/sn-react-usersearch/.env @@ -1 +1,3 @@ SKIP_PREFLIGHT_CHECK=true +REACT_APP_CLIENT_ID=KxkgOTAxLLCicUx7 +REACT_APP_CLIENT_SECRET= diff --git a/examples/sn-react-usersearch/src/app.tsx b/examples/sn-react-usersearch/src/app.tsx index 0638e7c7e..e500e7477 100644 --- a/examples/sn-react-usersearch/src/app.tsx +++ b/examples/sn-react-usersearch/src/app.tsx @@ -17,8 +17,6 @@ import UserSearch from './components/user-search' * The main entry point of your app. You can start h@cking from here ;) */ export const App: React.FunctionComponent = () => { - // const usr = useCurrentUser() - // const repo = useRepository() return ( diff --git a/examples/sn-react-usersearch/src/components/app-providers.tsx b/examples/sn-react-usersearch/src/components/app-providers.tsx index e0415c375..d87b30ddb 100644 --- a/examples/sn-react-usersearch/src/components/app-providers.tsx +++ b/examples/sn-react-usersearch/src/components/app-providers.tsx @@ -1,40 +1,35 @@ -import { AuthenticationProvider, useOidcAuthentication } from '@sensenet/authentication-oidc-react' +import { codeLogin, CodeLoginResponse } from '@sensenet/authentication-oidc-react' import { Repository } from '@sensenet/client-core' import { RepositoryContext } from '@sensenet/hooks-react' -import React, { PropsWithChildren } from 'react' -import { BrowserRouter, useHistory } from 'react-router-dom' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' import { configuration, repositoryUrl } from '../configuration' -import { LoginForm } from './login-form' +import { FullScreenLoader } from './full-screen-loader' export function AppProviders({ children }: PropsWithChildren<{}>) { return ( - - {children} - + {children} ) } -export const AuthProvider = ({ children }: PropsWithChildren<{}>) => { - const history = useHistory() - - return ( - - {children} - - ) -} - export const RepositoryProvider = ({ children }: PropsWithChildren<{}>) => { - const { oidcUser } = useOidcAuthentication() + const [authData, setAuthData] = useState() + + useEffect(() => { + ;(async () => { + const response = await codeLogin(configuration) + setAuthData(response) + })() + }, []) - if (!oidcUser) { - return + if (!authData) { + return } return ( - + {children} ) diff --git a/examples/sn-react-usersearch/src/components/header.tsx b/examples/sn-react-usersearch/src/components/header.tsx index 194937445..05898cc1b 100644 --- a/examples/sn-react-usersearch/src/components/header.tsx +++ b/examples/sn-react-usersearch/src/components/header.tsx @@ -1,13 +1,8 @@ -// start of material imports -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' import AppBar from '@material-ui/core/AppBar' -import IconButton from '@material-ui/core/IconButton' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import Toolbar from '@material-ui/core/Toolbar' import Typography from '@material-ui/core/Typography' -import LogoutIcon from '@material-ui/icons/PowerSettingsNew' import React from 'react' -// end of material imports const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -33,7 +28,6 @@ const useStyles = makeStyles((theme: Theme) => ) const HeaderPanel = () => { - const { logout, oidcUser } = useOidcAuthentication() const classes = useStyles() return ( @@ -41,16 +35,8 @@ const HeaderPanel = () => { - {oidcUser?.profile.name} + User search - - - diff --git a/examples/sn-react-usersearch/src/components/login-form.tsx b/examples/sn-react-usersearch/src/components/login-form.tsx deleted file mode 100644 index 82d50a21e..000000000 --- a/examples/sn-react-usersearch/src/components/login-form.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useOidcAuthentication } from '@sensenet/authentication-oidc-react' -import { Button, Paper, Typography } from '@material-ui/core' -import React from 'react' -import { repositoryUrl } from '../configuration' - -export const LoginForm = () => { - const { login } = useOidcAuthentication() - - return ( -
- - Login to {repositoryUrl} - -
- -
-
-
- ) -} diff --git a/examples/sn-react-usersearch/src/components/user-search.tsx b/examples/sn-react-usersearch/src/components/user-search.tsx index ae2bd1049..bb0b245fd 100644 --- a/examples/sn-react-usersearch/src/components/user-search.tsx +++ b/examples/sn-react-usersearch/src/components/user-search.tsx @@ -109,7 +109,7 @@ const UserSearchPanel = () => { const sendRequest = async () => { const result = await repo.loadCollection({ - path: `/Root/IMS`, + path: `/Root/IMS/Public`, oDataOptions: { metadata: 'no', inlinecount: 'allpages', diff --git a/examples/sn-react-usersearch/src/configuration.ts b/examples/sn-react-usersearch/src/configuration.ts index 62e7aea02..1ad42a02d 100644 --- a/examples/sn-react-usersearch/src/configuration.ts +++ b/examples/sn-react-usersearch/src/configuration.ts @@ -1,14 +1,10 @@ -import { UserManagerSettings } from '@sensenet/authentication-oidc-react' +import { CodeLoginParams } from '@sensenet/authentication-oidc-react' -export const repositoryUrl = 'https://dev.demo.sensenet.com/' +export const repositoryUrl = 'https://dev.demo.sensenet.com' -export const configuration: UserManagerSettings = { - client_id: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com - redirect_uri: `${window.location.origin}/authentication/callback`, - response_type: 'code', - post_logout_redirect_uri: `${window.location.origin}/`, - scope: 'openid profile sensenet', - authority: 'https://is.demo.sensenet.com/', - silent_redirect_uri: `${window.location.origin}/authentication/silent_callback`, - extraQueryParams: { snrepo: repositoryUrl }, +export const configuration: CodeLoginParams = { + clientId: process.env.REACT_APP_CLIENT_ID ?? '', // businesscat clientId for dev.demo.sensenet.com + clientSecret: process.env.REACT_APP_CLIENT_SECRET ?? '', + identityServerUrl: 'https://is.demo.sensenet.com', + appId: '7cYLChuhJxyGb7BS', //externalSPA clientId for dev.demo.sensenet.com } diff --git a/examples/sn-react-usersearch/tsconfig.json b/examples/sn-react-usersearch/tsconfig.json index 3a301d5c9..663219d18 100644 --- a/examples/sn-react-usersearch/tsconfig.json +++ b/examples/sn-react-usersearch/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -13,7 +17,11 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react" + "jsx": "react-jsx", + "noFallthroughCasesInSwitch": true }, - "include": ["src", "../../typings"] + "include": [ + "src", + "../../typings" + ] } diff --git a/packages/sn-authentication-oidc-react/src/code-login.ts b/packages/sn-authentication-oidc-react/src/code-login.ts new file mode 100644 index 000000000..7b67b7e92 --- /dev/null +++ b/packages/sn-authentication-oidc-react/src/code-login.ts @@ -0,0 +1,52 @@ +import { SigninResponse } from 'oidc-client' + +export type CodeLoginResponse = Pick + +export interface CodeLoginParams { + clientId: string + clientSecret: string + identityServerUrl: string + userId?: string | number + appId?: string + fetchMethod?: WindowOrWorkerGlobalScope['fetch'] +} + +export const codeLogin = async ({ + clientId, + clientSecret, + identityServerUrl, + userId, + appId, + fetchMethod = window && window.fetch && window.fetch.bind(window), +}: CodeLoginParams) => { + const configuration = { + client_id: clientId, + client_secret: clientSecret, + grant_type: 'client_credentials', + scope: encodeURIComponent('sensenet'), + } + + const requestBody = Object.entries(configuration).reduce((acc, current, idx) => { + return `${acc}${current[0]}=${current[1]}${idx === Object.entries(configuration).length - 1 ? '' : '&'}` + }, '') + + const url = `${identityServerUrl}/connect/token` + const options = { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: requestBody, + } + + const response = await fetchMethod(url, options) + const authData: CodeLoginResponse = (await response.json?.()) ?? response.body ?? response + + if (userId && authData) { + authData.profile = { + sub: userId, + } + } + + appId && window?.sessionStorage?.setItem(`oidc.user:${identityServerUrl}:${appId}`, JSON.stringify(authData)) + + return authData +} diff --git a/packages/sn-authentication-oidc-react/src/index.ts b/packages/sn-authentication-oidc-react/src/index.ts index a27a07151..862524963 100644 --- a/packages/sn-authentication-oidc-react/src/index.ts +++ b/packages/sn-authentication-oidc-react/src/index.ts @@ -2,4 +2,5 @@ import { UserManager, UserManagerSettings, WebStorageStateStore } from 'oidc-cli export * from './components/authentication-provider' export * from './components/oidc-secure' export * from './use-authentication' +export * from './code-login' export { WebStorageStateStore, UserManagerSettings, UserManager } From 8f556a009d8340f72e96d05941907c3b78af1ae0 Mon Sep 17 00:00:00 2001 From: Zoltan Takacs Date: Thu, 22 Apr 2021 14:32:05 +0200 Subject: [PATCH 2/3] remove unit test --- .../sn-react-browser/test/navbar.test.tsx | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 examples/sn-react-browser/test/navbar.test.tsx diff --git a/examples/sn-react-browser/test/navbar.test.tsx b/examples/sn-react-browser/test/navbar.test.tsx deleted file mode 100644 index e7d75eda7..000000000 --- a/examples/sn-react-browser/test/navbar.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable react/display-name */ - -import Button from '@material-ui/core/Button' -import { mount } from 'enzyme' -import React, { PropsWithChildren } from 'react' -import { AppProviders } from '../src/components/app-providers' -import { NavBarComponent } from '../src/components/navbar' - -const logout = jest.fn() -jest.mock('@sensenet/authentication-oidc-react', () => ({ - useOidcAuthentication: () => ({ oidcUser: { access_token: 'token' }, logout }), - AuthenticationProvider: ({ children }: PropsWithChildren<{}>) => <>{children}, -})) - -const NavBar = () => ( - - - -) - -describe('The navbar instance', () => { - it('should logout the user correctly', () => { - const wrapper = mount() - - wrapper.find(Button).simulate('click') - expect(logout).toBeCalled() - }) -}) From 9a364b5cce33967b1c3508271c1bf9c52d707bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tak=C3=A1cs?= Date: Tue, 27 Apr 2021 14:29:07 +0200 Subject: [PATCH 3/3] add unit tests --- .../test/code-login.test.tsx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/sn-authentication-oidc-react/test/code-login.test.tsx diff --git a/packages/sn-authentication-oidc-react/test/code-login.test.tsx b/packages/sn-authentication-oidc-react/test/code-login.test.tsx new file mode 100644 index 000000000..77ecd336b --- /dev/null +++ b/packages/sn-authentication-oidc-react/test/code-login.test.tsx @@ -0,0 +1,47 @@ +import { codeLogin } from '../src/code-login' + +declare const global: any +global.window = {} + +describe('CodeLogin', () => { + const loginParams = { clientId: 'test', clientSecret: 'test', identityServerUrl: 'https://is.test.com' } + + const mockData = { + access_token: 'eyJhbGciOiJSUUclFOZmVmODlYZHhSVVMtNkkdCtqd3QifQ.eyJuYmYiOjE2MTk1Mj4eg7dmeDMJ0aow9ewPqleqdSpHwl', + expires_in: 3600, + scope: 'sensenet', + token_type: 'Bearer', + } + + const mockResponse = { + ok: true, + json: async () => mockData, + } + + const fetchMock: any = async () => { + return mockResponse + } + + it('Should be constructed with a built-in fetch method', () => { + global.window.fetch = jest.fn(() => mockResponse) + codeLogin(loginParams) + expect(global.window.fetch).toBeCalled() + + expect(global.window.fetch).toHaveBeenCalledWith('https://is.test.com/connect/token', { + body: 'client_id=test&client_secret=test&grant_type=client_credentials&scope=sensenet', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + method: 'POST', + }) + }) + + it('should resolve with a proper response', async () => { + const resp = await codeLogin({ ...loginParams, fetchMethod: fetchMock }) + expect(resp).toEqual(mockData) + }) + + it('should resolve with userId', async () => { + const responseData = { ...mockData, profile: { sub: 1 } } + const resp = await codeLogin({ ...loginParams, fetchMethod: fetchMock, userId: 1 }) + expect(resp).toEqual(responseData) + }) +})