Displays the current authentication mechanism in the admin panel (#981)

* Adds authentication mechanism to admin panel

Adds field to the "Security settings" admin display, showing the
currently configured authentication mechanism.

* Adds 14px margin to admin panel names
This commit is contained in:
Spoffy 2024-05-16 18:09:38 +01:00 committed by GitHub
parent d8f4e075fe
commit b4acb157f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 67 additions and 1 deletions

View File

@ -105,6 +105,13 @@ export class AdminPanel extends Disposable {
value: this._buildSandboxingDisplay(owner), value: this._buildSandboxingDisplay(owner),
expandedContent: this._buildSandboxingNotice(), expandedContent: this._buildSandboxingNotice(),
}), }),
dom.create(AdminSectionItem, {
id: 'authentication',
name: t('Authentication'),
description: t('Current authentication method'),
value: this._buildAuthenticationDisplay(owner),
expandedContent: this._buildAuthenticationNotice(owner),
})
]), ]),
dom.create(AdminSection, t('Version'), [ dom.create(AdminSection, t('Version'), [
@ -156,6 +163,37 @@ isolated from other documents and isolated from the network.'),
]; ];
} }
private _buildAuthenticationDisplay(owner: IDisposableOwner) {
return dom.domComputed(
use => {
const req = this._checks.requestCheckById(use, 'authentication');
const result = req ? use(req.result) : undefined;
if (!result) {
return cssValueLabel(cssErrorText('unavailable'));
}
const { success, details } = result;
const loginSystemId = details?.loginSystemId;
if (!success || !loginSystemId) {
return cssValueLabel(cssErrorText('auth error'));
}
if (loginSystemId === 'no-logins') {
return cssValueLabel(cssDangerText('no authentication'));
}
return cssValueLabel(cssHappyText(loginSystemId));
}
);
}
private _buildAuthenticationNotice(owner: IDisposableOwner) {
return t('Grist allows different types of authentication to be configured, including SAML and OIDC. \
We recommend enabling one of these if Grist is accessible over the network or being made available \
to multiple people.');
}
private _buildUpdates(owner: MultiHolder) { private _buildUpdates(owner: MultiHolder) {
// We can be in those states: // We can be in those states:
enum State { enum State {
@ -446,6 +484,10 @@ const cssErrorText = styled('span', `
color: ${theme.errorText}; color: ${theme.errorText};
`); `);
export const cssDangerText = styled('div', `
color: ${theme.dangerText};
`);
const cssHappyText = styled('span', ` const cssHappyText = styled('span', `
color: ${theme.controlFg}; color: ${theme.controlFg};
`); `);

View File

@ -124,6 +124,7 @@ const cssItemName = styled('div', `
font-weight: bold; font-weight: bold;
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 14px;
font-size: ${vars.largeFontSize}; font-size: ${vars.largeFontSize};
padding-left: 24px; padding-left: 24px;
&-prefixed { &-prefixed {

View File

@ -6,7 +6,8 @@ export type BootProbeIds =
'reachable' | 'reachable' |
'host-header' | 'host-header' |
'sandboxing' | 'sandboxing' |
'system-user' 'system-user' |
'authentication'
; ;
export interface BootProbeResult { export interface BootProbeResult {

View File

@ -58,6 +58,7 @@ export class BootProbes {
this._probes.push(_bootProbe); this._probes.push(_bootProbe);
this._probes.push(_hostHeaderProbe); this._probes.push(_hostHeaderProbe);
this._probes.push(_sandboxingProbe); this._probes.push(_sandboxingProbe);
this._probes.push(_authenticationProbe);
this._probeById = new Map(this._probes.map(p => [p.id, p])); this._probeById = new Map(this._probes.map(p => [p.id, p]));
} }
} }
@ -202,3 +203,17 @@ const _sandboxingProbe: Probe = {
}; };
}, },
}; };
const _authenticationProbe: Probe = {
id: 'authentication',
name: 'Authentication system',
apply: async(server, req) => {
const loginSystemId = server.getInfo('loginMiddlewareComment');
return {
success: loginSystemId != undefined,
details: {
loginSystemId,
}
};
},
};

View File

@ -1426,6 +1426,11 @@ export class FlexServer implements GristServer {
return this._sandboxInfo; return this._sandboxInfo;
} }
public getInfo(key: string): any {
const infoPair = this.info.find(([keyToCheck]) => key === keyToCheck);
return infoPair?.[1];
}
public disableExternalStorage() { public disableExternalStorage() {
if (this.deps.has('doc')) { if (this.deps.has('doc')) {
throw new Error('disableExternalStorage called too late'); throw new Error('disableExternalStorage called too late');

View File

@ -67,6 +67,7 @@ export interface GristServer {
getBundledWidgets(): ICustomWidget[]; getBundledWidgets(): ICustomWidget[];
hasBoot(): boolean; hasBoot(): boolean;
getSandboxInfo(): SandboxInfo|undefined; getSandboxInfo(): SandboxInfo|undefined;
getInfo(key: string): any;
} }
export interface GristLoginSystem { export interface GristLoginSystem {
@ -159,6 +160,7 @@ export function createDummyGristServer(): GristServer {
getBundledWidgets() { return []; }, getBundledWidgets() { return []; },
hasBoot() { return false; }, hasBoot() { return false; },
getSandboxInfo() { return undefined; }, getSandboxInfo() { return undefined; },
getInfo(key: string) { return undefined; }
}; };
} }