6.6 KiB
authAutomat
Source File:
src/automats/authAutomat.js
Registration Key:'auth'(Automat.get('auth')/window.__AUTOMATS__.get('auth'))
1. Overview & Purpose
authAutomat manages the authentication and session lifecycle of the Dashfront client, as well as tenant company selection. It acts as the root coordinator for all other configuration and catalog automats (companyAutomat, categoryAutomat, brandAutomat, unitAutomat, posterminalAutomat, supplierAutomat).
Core Responsibilities
- Session Lifecycle: Handles transitioning through
'loading'→'authed'or'anon'. - Google One Tap / GIS Integration: Exchanging Google OAuth JWTs for session cookies via
/api/auth/google. - Multi-Tenant Company Switching: Updating the active company context via
/api/auth/selected-companyand synchronizing user state. - Event Dispatching: Emits cross-subsystem events for session establishment, company changes, and logout.
- Automatic Sub-Store Bootstrap: Downstream configuration automats subscribe to
authAutomatto load or clear themselves reactively.
2. State Structure
interface AuthUser {
id?: string | number;
googleAuthId?: string;
email?: string;
name?: string;
picture?: string | null;
selectedCompanyId?: string | number | null;
companies?: Array<{ id: string | number; name: string }>;
[key: string]: any;
}
interface AuthState {
/** Authenticated user profile, or null when anonymous. */
user: AuthUser | null;
/** Google OAuth Client ID provided by backend bootstrap. */
googleClientId: string | null;
/** Current authentication lifecycle phase. */
status: 'loading' | 'anon' | 'authed';
}
Initial State
const initialState = {
user: null,
googleClientId: null,
status: 'loading',
};
3. Configuration & Options
new Automat(initialState, actions, { name: 'auth' });
name:'auth'persist:false(session token resides in secure HTTP-only cookies).
4. Lifecycle Events & Dispatcher
Constants exported from authAutomat:
AUTH_SESSION_ESTABLISHED = 'auth/sessionEstablished': Fired when a valid session is recognized.AUTH_COMPANY_CHANGED = 'auth/companyChanged': Fired when the active company changes or a new company is created.AUTH_LOGGED_OUT = 'auth/loggedOut': Fired when user logs out.
Event Dispatcher Registration
import { setAuthEventDispatcher } from '../automats/authAutomat';
// Allows routing events to an external system, logger, or telemetry
setAuthEventDispatcher((event) => {
console.log('[Auth Event]', event.type);
});
5. Actions & Methods
Access actions via authAutomat.actions.<method>().
bootstrapSession(session?: object): Promise<object>
Initializes the session on application startup:
- If
sessionis passed directly, uses it; otherwise fetchesGET /api/auth/me. - Updates
googleClientId. - If no user is returned, sets status to
'anon'. - If a user is returned, merges the profile, normalizes base64 avatar pictures, sets status to
'authed', and emitsAUTH_SESSION_ESTABLISHED.
loginWithGoogle(credential: string): Promise<object>
Submits a Google One Tap JWT to POST /api/auth/google. On success, updates user state, sets status to 'authed', and emits AUTH_SESSION_ESTABLISHED.
selectCompany(companyId: string | number): Promise<AuthUser>
Calls PUT /api/auth/selected-company to switch active company context in the backend session cookie. Updates state.user.selectedCompanyId, triggers AUTH_COMPANY_CHANGED, and returns the updated user.
createCompany(payload: object): Promise<AuthUser>
Creates a new tenant company via POST /api/company and automatically selects it for the session. Emits AUTH_COMPANY_CHANGED.
logout(): Promise<void>
Calls POST /api/auth/logout, replaces history state to '/', clears user state, sets status to 'anon', and emits AUTH_LOGGED_OUT.
6. Cross-Automat Coordination
Downstream automats coordinate with authAutomat at module load time. For example:
// In companyAutomat.js / brandAutomat.js / unitAutomat.js / posterminalAutomat.js:
authAutomat.subscribe((auth) => {
const companyId = auth.user?.selectedCompanyId;
if (auth.status === 'authed' && companyId) {
void targetAutomat.actions.load();
} else if (auth.status === 'anon') {
targetAutomat.actions.reset();
}
});
When auth.status transitions to 'authed' with a selectedCompanyId, all dependent stores load their data. On logout ('anon'), they clear their state.
7. Usage Examples
A. React PureComponent Login Gate / User Menu
import { PureComponent } from 'react';
import authAutomat from '../automats/authAutomat';
export class UserMenu extends PureComponent {
constructor(props) {
super(props);
this.state = {
user: authAutomat.state.user,
status: authAutomat.state.status,
};
}
componentDidMount() {
this.unsub = authAutomat.subscribe(this, ['user', 'status']);
}
componentWillUnmount() {
this.unsub?.();
}
handleCompanyChange = (e) => {
authAutomat.actions.selectCompany(e.target.value);
};
handleLogout = () => {
authAutomat.actions.logout();
};
render() {
const { user, status } = this.state;
if (status === 'loading') return <div>Authentifizierung...</div>;
if (status === 'anon' || !user) return <div>Nicht angemeldet</div>;
return (
<div>
<span>{user.name} ({user.email})</span>
<select value={user.selectedCompanyId ?? ''} onChange={this.handleCompanyChange}>
{user.companies?.map((c) => (
<option key={c.id} value={c.id}>{c.name}</option>
))}
</select>
<button onClick={this.handleLogout}>Abmelden</button>
</div>
);
}
}
B. Application Bootstrap in AuthBootstrap.jsx
import authAutomat from '../automats/authAutomat';
async function initApp() {
try {
await authAutomat.actions.bootstrapSession();
} catch (err) {
console.error('Session bootstrap failed:', err);
}
}
8. Best Practices & Gotchas
- Never pass
googleAuthId: The frontend must never sendgoogleAuthIdin request bodies or query params. Identity is extracted from the session cookie on the server. - Handle
'loading'State: Do not render authentication-required views whileauthAutomat.state.status === 'loading'. - Picture normalization:
authAutomatautomatically formats raw base64 picture strings into validdata:image/...;base64,...URIs.