Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GOOGLE_API_KEY = secret
GOOGLE_CLIENT_ID = secret
MICROSOFT_API_KEY = secret
MICROSOFT_CLIENT_ID = secret

AUTH_HEADER = secret
URL_ORIGIN = lara.exampleCompany
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/reusable-build-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
DEBUG: ${{ inputs.debug }}
MODE: ${{ inputs.mode }}
AUTH_HEADER: ${{ secrets.AUTH_HEADER }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
MICROSOFT_TENANT_ID: ${{ secrets.MICROSOFT_TENANT_ID }}
SUPPORT_MAIL: ${{ secrets.SUPPORT_MAIL }}
URL_ORIGIN: ${{ secrets.URL_ORIGIN }}
COMPANY_ABBREVIATION: ${{ secrets.COMPANY_ABBREVIATION }}
Expand Down Expand Up @@ -55,6 +55,18 @@ jobs:
if: env.ENVIRONMENT_NAME == 'staging'
run: |
echo "BACKEND_URL=${{ secrets.STAGING_BE_URL }}" >> $GITHUB_ENV

##############
# Set MICROSOFT CLIENT_ID env variable this way, so not all variables have to be passed to the reusable workflow
- name: Set production MICROSOFT CLIENT_ID
if: env.ENVIRONMENT_NAME == 'production'
run: |
echo "MICROSOFT_CLIENT_ID=${{ secrets.PROD_MICROSOFT_CLIENT_ID }}" >> $GITHUB_ENV
- name: Set staging MICROSOFT CLIENT_ID
if: env.ENVIRONMENT_NAME == 'staging'
run: |
echo "MICROSOFT_CLIENT_ID=${{ secrets.STAGING_MICROSOFT_CLIENT_ID }}" >> $GITHUB_ENV

##############
- name: Compile and build
run: yarn clean && yarn compile && yarn build
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/reusable-deploy-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ jobs:
AUTH_HEADER: ${{ secrets.AUTH_HEADER }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
MICROSOFT_TENANT_ID: ${{ secrets.MICROSOFT_TENANT_ID }}
LARA_SECRET: ${{ secrets.LARA_SECRET }}
SES_EMAIL: ${{ secrets.SES_EMAIL }}
SUPPORT_MAIL: ${{ secrets.SUPPORT_MAIL }}
Expand Down Expand Up @@ -77,6 +76,17 @@ jobs:
echo "FRONTEND_URL=${{ secrets.STAGING_FE_URL }}" >> $GITHUB_ENV
echo "BACKEND_URL=${{ secrets.STAGING_BE_URL }}" >> $GITHUB_ENV

##############
# Set MICROSOFT CLIENT_ID env variable this way, so not all variables have to be passed to the reusable workflow
- name: Set production MICROSOFT CLIENT_ID
if: env.ENVIRONMENT_NAME == 'production'
run: |
echo "MICROSOFT_CLIENT_ID=${{ secrets.PROD_MICROSOFT_CLIENT_ID }}" >> $GITHUB_ENV
- name: Set staging MICROSOFT CLIENT_ID
if: env.ENVIRONMENT_NAME == 'staging'
run: |
echo "MICROSOFT_CLIENT_ID=${{ secrets.STAGING_MICROSOFT_CLIENT_ID }}" >> $GITHUB_ENV

- name: Deploy Frontend
run: serverless s3sync bucket --bucket ${{ secrets.COMPANY_ABBREVIATION }}-lara-frontend-${{ inputs.target }}

Expand Down
4 changes: 2 additions & 2 deletions packages/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ type Mutation {
deleteEntry(id: ID!): MutateEntryPayload!

"""
Login via google.
Login via microsoft
"""
login(googleToken: String!): OAuthPayload
login(email: String!): OAuthPayload

"""
Unclaims a Trainee by the current Trainer
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export type GqlMutation = {
deleteEntry: GqlMutateEntryPayload;
/** Link Alexa account */
linkAlexa?: Maybe<GqlUserInterface>;
/** Login via google. */
/** Login via microsoft */
login?: Maybe<GqlOAuthPayload>;
/** Marks User to be deleted */
markUserForDeletion?: Maybe<GqlUserInterface>;
Expand Down Expand Up @@ -256,7 +256,7 @@ export type GqlMutationLinkAlexaArgs = {


export type GqlMutationLoginArgs = {
googleToken: Scalars['String'];
email: Scalars['String'];
};


Expand Down Expand Up @@ -770,7 +770,7 @@ export type GqlMutationResolvers<ContextType = Context, ParentType extends GqlRe
createTrainer?: Resolver<Maybe<GqlResolversTypes['Trainer']>, ParentType, ContextType, RequireFields<GqlMutationCreateTrainerArgs, 'input'>>;
deleteEntry?: Resolver<GqlResolversTypes['MutateEntryPayload'], ParentType, ContextType, RequireFields<GqlMutationDeleteEntryArgs, 'id'>>;
linkAlexa?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationLinkAlexaArgs, 'code' | 'state'>>;
login?: Resolver<Maybe<GqlResolversTypes['OAuthPayload']>, ParentType, ContextType, RequireFields<GqlMutationLoginArgs, 'googleToken'>>;
login?: Resolver<Maybe<GqlResolversTypes['OAuthPayload']>, ParentType, ContextType, RequireFields<GqlMutationLoginArgs, 'email'>>;
markUserForDeletion?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType, RequireFields<GqlMutationMarkUserForDeletionArgs, 'id'>>;
unclaimTrainee?: Resolver<Maybe<GqlResolversTypes['TrainerTraineePayload']>, ParentType, ContextType, RequireFields<GqlMutationUnclaimTraineeArgs, 'id'>>;
unlinkAlexa?: Resolver<Maybe<GqlResolversTypes['UserInterface']>, ParentType, ContextType>;
Expand Down
15 changes: 0 additions & 15 deletions packages/backend/src/google-auth.ts

This file was deleted.

5 changes: 1 addition & 4 deletions packages/backend/src/resolvers/auth.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { GraphQLError } from 'graphql'

import { GqlResolvers } from '@lara/api'

import { validateToken } from '../google-auth'
import { t } from '../i18n'
import { isDebug } from '../permissions'
import { saveUser, userByEmail, userById } from '../repositories/user.repo'
Expand All @@ -27,9 +26,7 @@ const createMockUser = async (email: string) => {

export const authResolver: GqlResolvers = {
Mutation: {
login: async (_parent, { googleToken }) => {
const email = await validateToken(googleToken)

login: async (_parent, { email }) => {
if (!email) {
return
}
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/test/getEnv.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ process.env = {

GOOGLE_API_KEY: 'secret',
GOOGLE_CLIENT_ID: 'secret',

MICROSOFT_CLIENT_ID: 'secret',
MICROSOFT_TENANT_ID: 'secret',
AUTH_HEADER: 'secret',
DEBUG: true,

Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
},
"dependencies": {
"@apollo/client": "^3.2.2",
"@azure/msal-browser": "^2.37.1",
"@azure/msal-react": "^1.5.8",
"@lara/components": "^1.0.0",
"@react-oauth/google": "^0.5.1",
"@microsoft/microsoft-graph-client": "^3.0.5",
"@rebass/grid": "^6.1.0",
"apollo-link-token-refresh": "^0.4.0",
"date-fns": "^2.16.1",
Expand Down
31 changes: 17 additions & 14 deletions packages/frontend/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import React from 'react'
import { Router, Switch } from 'react-router-dom'
import { GoogleOAuthProvider } from '@react-oauth/google'

import ApolloProvider from './apollo-provider'
import AppHistory from './app-history'
import { GlobalFonts } from './components/fonts'
import StatusBar from './components/status-bar'
import { ToastContextProvider } from './context/toast/toast-context-provider'
import { useCurrentUserQuery } from './graphql'
import { AuthenticatedState, AuthenticationContext } from './hooks/use-authentication'
import { SplashPage } from './pages/splash-page'
import Routes from './routes'
import ThemeProvider from './theme-provider'
import { AuthenticatedState, AuthenticationContext } from './hooks/use-authentication'
import { GlobalFonts } from './components/fonts'
import { MsalProvider } from '@azure/msal-react'
import { msalConfig } from './hooks/ms-auth'
import { PublicClientApplication } from '@azure/msal-browser'
import { SplashPage } from './pages/splash-page'
import { ToastContextProvider } from './context/toast/toast-context-provider'
import { useCurrentUserQuery } from './graphql'

const msalInstance = new PublicClientApplication(msalConfig)
export const App: React.FunctionComponent = () => {
const [authenticated, setAuthenticated] = React.useState<AuthenticatedState>('loading')

return (
<AuthenticationContext.Provider value={{ authenticated, setAuthenticated }}>
<GoogleOAuthProvider clientId={ENVIRONMENT.googleClientID}>
<MsalProvider instance={msalInstance}>
<AuthenticationContext.Provider value={{ authenticated, setAuthenticated }}>
<ApolloProvider>
<InnerApp />
</ApolloProvider>
</GoogleOAuthProvider>
</AuthenticationContext.Provider>
</AuthenticationContext.Provider>
</MsalProvider>
)
}

Expand All @@ -36,19 +38,20 @@ const InnerApp: React.FunctionComponent = () => {
* the redundant api call
*/
const { data, loading } = useCurrentUserQuery()
const currentUser = data?.currentUser

return (
<ThemeProvider currentUser={data?.currentUser}>
<ThemeProvider currentUser={currentUser}>
{loading && <SplashPage />}

{!loading && (
<ToastContextProvider>
<Router history={AppHistory.getInstance()}>
<Switch>
<Routes currentUser={data?.currentUser} />
<Routes currentUser={currentUser} />
</Switch>

{ENVIRONMENT.debug && <StatusBar currentUser={data?.currentUser} />}
{ENVIRONMENT.debug && <StatusBar currentUser={currentUser} />}
</Router>
</ToastContextProvider>
)}
Expand Down
40 changes: 40 additions & 0 deletions packages/frontend/src/components/ms-sign-in-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { ButtonHTMLAttributes } from 'react'
import AppHistory from '../app-history'
import { PrimaryButton } from './button'
import { useMsal } from '@azure/msal-react'
import { loginRequest } from '../hooks/ms-auth'
import { useAuthentication } from '../hooks/use-authentication'
import { useLoginPageLoginMutation } from '../graphql'
import { useIsAuthenticated } from '@azure/msal-react'

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
fullsize?: boolean
}

export const SignInButton: React.FunctionComponent<ButtonProps> = () => {
const { instance } = useMsal()
const { login } = useAuthentication()
const [mutate] = useLoginPageLoginMutation()
const isAuthenticated = useIsAuthenticated()

const handleLogin = async () => {
const loginResponse = await instance.loginPopup(loginRequest)
if (isAuthenticated) {
const email = loginResponse.account?.username
if (email) {
mutate({ variables: { email } })
.then((response) => {
const { data } = response
if (!data?.login) {
return AppHistory.getInstance().push('/no-user-found')
}
login(data.login)
})
.catch((err) => {
console.log(err)
})
}
}
}
return <PrimaryButton onClick={() => handleLogin()}>Sign in with Microsoft</PrimaryButton>
}
4 changes: 3 additions & 1 deletion packages/frontend/src/frontend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import ReactDOM from 'react-dom'

import { App } from './app'

ReactDOM.render(<App />, document.getElementById('app'))
if (window.location.hash === '') {
ReactDOM.render(<App />, document.getElementById('app'))
}

if (module.hot && ENVIRONMENT.debug) {
module.hot.accept()
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ declare module '*.png'
declare const ENVIRONMENT: {
name: string
debug: boolean
googleClientID: string
microsoftClientID: string
microsoftTenantID: string
authHeader: string
backendUrl: string
frontendUrl: string
supportMail: string
}

Expand Down
10 changes: 5 additions & 5 deletions packages/frontend/src/graphql/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export type Mutation = {
deleteEntry: MutateEntryPayload;
/** Link Alexa account */
linkAlexa?: Maybe<UserInterface>;
/** Login via google. */
/** Login via microsoft */
login?: Maybe<OAuthPayload>;
/** Marks User to be deleted */
markUserForDeletion?: Maybe<UserInterface>;
Expand Down Expand Up @@ -255,7 +255,7 @@ export type MutationLinkAlexaArgs = {


export type MutationLoginArgs = {
googleToken: Scalars['String'];
email: Scalars['String'];
};


Expand Down Expand Up @@ -637,7 +637,7 @@ export type LinkAlexaMutationVariables = Exact<{
export type LinkAlexaMutation = { __typename?: 'Mutation', linkAlexa?: { __typename?: 'Admin', id: string, alexaSkillLinked?: boolean | undefined } | { __typename?: 'Trainee', id: string, alexaSkillLinked?: boolean | undefined } | { __typename?: 'Trainer', id: string, alexaSkillLinked?: boolean | undefined } | undefined };

export type LoginPageLoginMutationVariables = Exact<{
token: Scalars['String'];
email: Scalars['String'];
}>;


Expand Down Expand Up @@ -1136,8 +1136,8 @@ export function useLinkAlexaMutation(baseOptions?: Apollo.MutationHookOptions<Li
}
export type LinkAlexaMutationHookResult = ReturnType<typeof useLinkAlexaMutation>;
export const LoginPageLoginDocument = gql`
mutation LoginPageLogin($token: String!) {
login(googleToken: $token) {
mutation LoginPageLogin($email: String!) {
login(email: $email) {
accessToken
refreshToken
expiresIn
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/graphql/mutations/login-page-login.gql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mutation LoginPageLogin($token: String!) {
login(googleToken: $token) {
mutation LoginPageLogin($email: String!) {
login(email: $email) {
accessToken
refreshToken
expiresIn
Expand Down
15 changes: 15 additions & 0 deletions packages/frontend/src/hooks/ms-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const msalConfig = {
auth: {
clientId: ENVIRONMENT.microsoftClientID ?? '',
authority: 'https://login.microsoftonline.com/' + ENVIRONMENT.microsoftTenantID,
redirectUri: ENVIRONMENT.frontendUrl,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
}

export const loginRequest = {
scopes: ['User.Read', 'openid', 'profile'],
}
Loading