Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(authenticator): fetch current user on routed apps after refresh #1580

Merged
merged 18 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
8 changes: 8 additions & 0 deletions .changeset/five-crabs-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@aws-amplify/ui-react": patch
"@aws-amplify/ui": patch
"@aws-amplify/ui-vue": patch
"@aws-amplify/ui-angular": patch
---

fix(authenticator): look for current user on routed apps whenever app refreshes
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class AuthenticatorComponent implements OnInit, AfterContentInit {
public signInTitle = translate('Sign In');
public signUpTitle = translate('Create Account');

private hasInitialized = false;

constructor(
private authenticator: AuthenticatorService,
private contextService: CustomComponentsService
Expand All @@ -54,13 +56,24 @@ export class AuthenticatorComponent implements OnInit, AfterContentInit {
socialProviders,
formFields,
} = this;
this.authenticator.startMachine({
initialState,
loginMechanisms,
services,
signUpAttributes,
socialProviders,
formFields,

// send INIT event once machine is at 'setup' state
this.authenticator.subscribe(() => {
const { route } = this.authenticator;
if (!this.hasInitialized && route === 'setup') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously authMachine got stuck in idle state until an Authenticator in DOM sends an INIT event on mount. Now authMachine starts independently (see state machine changes below), and will explicitly ask for config from Authenticator once it's in setup state.

Once Authenticator sees that state is in setup, it'll send the usual INIT event.

this.authenticator.send({
type: 'INIT',
data: {
initialState,
loginMechanisms,
services,
signUpAttributes,
socialProviders,
formFields,
},
});
this.hasInitialized = true;
}
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Injectable, OnDestroy } from '@angular/core';
import { Logger } from '@aws-amplify/core';
import {
AuthContext,
AuthenticatorMachineOptions,
AuthEvent,
AuthInterpreter,
AuthMachineState,
Expand Down Expand Up @@ -31,30 +30,11 @@ export class AuthenticatorService implements OnDestroy {
private _hubSubscription: ReturnType<typeof listenToAuthHub>;
private _facade: ReturnType<typeof getServiceContextFacade>;

public startMachine({
initialState,
loginMechanisms,
services,
signUpAttributes,
socialProviders,
formFields,
}: AuthenticatorMachineOptions) {
constructor() {
const machine = createAuthenticatorMachine();

const authService = interpret(machine).start();

authService.send({
type: 'INIT',
data: {
initialState,
loginMechanisms,
socialProviders,
signUpAttributes,
services,
formFields,
},
});

this._machineSubscription = authService.subscribe((state) => {
this._authState = state;
this._facade = getServiceContextFacade(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function InitMachine({ children, ...data }) {
const hasInitialized = React.useRef(false);

React.useEffect(() => {
if (!hasInitialized.current && route === 'idle') {
if (!hasInitialized.current && route === 'setup') {
wlee221 marked this conversation as resolved.
Show resolved Hide resolved
_send({
type: 'INIT',
data,
Expand Down
56 changes: 33 additions & 23 deletions packages/ui/src/machines/authenticator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,58 @@ export function createAuthenticatorMachine() {
context: {
user: undefined,
config: {},
services: {},
services: { ...defaultServices },
wlee221 marked this conversation as resolved.
Show resolved Hide resolved
actorRef: undefined,
},
states: {
// See: https://xstate.js.org/docs/guides/communication.html#invoking-promises
idle: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idle state now starts with Auth.currentAuthenticatedUser call.

Knowing that, idle is a bad name (it's on me!) and should be named more explicitly. I scoped this out of this PR, because that would change our route names and I would rather deal with it in 3.0 changes.

on: {
INIT: {
invoke: {
src: 'getCurrentUser',
onDone: {
actions: 'setUser',
target: 'authenticated',
},
onError: {
target: 'setup',
actions: 'configure',
},
},
},
setup: {
invoke: [
{
// TODO Wait for Auth to be configured
src: 'getCurrentUser',
onDone: {
actions: 'setUser',
target: 'authenticated',
initial: 'waitConfig',
states: {
waitConfig: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this stage, we confirmed that no user has signed in yet. Now we wait for Authenticator to send INIT event to pass machine props like initialState.

on: {
INIT: {
actions: 'configure',
target: 'applyConfig',
},
},
onError: [
},
applyConfig: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have all the machine props ready, we apply those configs to get loginMechanisms, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

invoke: {
// TODO Wait for Auth to be configured
src: 'getAmplifyConfig',
onDone: {
actions: 'applyAmplifyConfig',
target: 'goToInitialState',
},
},
},
goToInitialState: {
always: [
{
target: 'signUp',
target: '#authenticator.signUp',
cond: 'isInitialStateSignUp',
},
{
target: 'resetPassword',
target: '#authenticator.resetPassword',
cond: 'isInitialStateResetPassword',
},
{ target: 'signIn' },
{ target: '#authenticator.signIn' },
],
},
{
src: 'getAmplifyConfig',
onDone: {
actions: 'applyAmplifyConfig',
},
},
],
},
},
signIn: {
initial: 'spawnActor',
Expand Down Expand Up @@ -220,7 +231,6 @@ export function createAuthenticatorMachine() {
}

// Prefer explicitly configured settings over default CLI values\

const {
loginMechanisms,
signUpAttributes,
Expand Down
32 changes: 21 additions & 11 deletions packages/vue/src/components/authenticator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,29 @@ let unsubscribeHub: ReturnType<typeof listenToAuthHub>;
const { state, send } = useActor(service);
useAuth(service);

const hasInitialized = ref(false);

service.subscribe((newState) => {
wlee221 marked this conversation as resolved.
Show resolved Hide resolved
if (newState.matches('setup')) {
if (!hasInitialized.value) {
wlee221 marked this conversation as resolved.
Show resolved Hide resolved
send({
type: 'INIT',
data: {
initialState,
loginMechanisms,
socialProviders,
signUpAttributes,
services,
formFields,
},
});
hasInitialized.value = true;
}
}
});

onMounted(() => {
unsubscribeHub = listenToAuthHub(send);
send({
type: 'INIT',
data: {
initialState,
loginMechanisms,
socialProviders,
signUpAttributes,
services,
formFields,
},
});
});

onUnmounted(() => {
Expand Down