Skip to content

Commit 13bcda4

Browse files
committed
divide lock & login screen ui and auth module
1 parent f64e08c commit 13bcda4

10 files changed

Lines changed: 352 additions & 146 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codam-web-greeter",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "LightDM greeter theme for Codam Coding College, compatible with nody-greeter and web-greeter",
55
"main": "main.js",
66
"scripts": {

public/styles.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ form input::placeholder {
175175
}
176176

177177
form input.wiggle {
178-
animation: wiggle 0.6s ease-in-out;
178+
animation: wiggle 0.6s ease-in-out 0s 1 normal;
179+
border-color: var(--color-red) !important;
179180
}
180181

181182
form button {

scripts/auth.ts

Lines changed: 94 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,52 @@
11
import { LightDMMessageType, LightDMPromptType, LightDMUser, lightdm } from 'nody-greeter-types/index';
22

3-
export interface UILoginElements {
4-
loginForm: HTMLFormElement;
5-
loginInput: HTMLInputElement;
6-
passwordInput: HTMLInputElement;
7-
loginButton: HTMLButtonElement;
8-
}
9-
10-
export interface UILockScreenElements {
11-
lockForm: HTMLFormElement;
12-
displayName: HTMLHeadingElement;
13-
loginName: HTMLHeadingElement;
14-
passwordInput: HTMLInputElement;
15-
unlockButton: HTMLButtonElement;
3+
export interface AuthenticatorEvents {
4+
/**
5+
* This event gets called when the login process starts without error.
6+
*/
7+
authenticationStart: () => void;
8+
9+
/**
10+
* This event gets called when the login process completes without error.
11+
*/
12+
authenticationComplete: () => void;
13+
14+
/**
15+
* This event gets called when the login process fails due to an authentication failure (wrong username or password).
16+
*/
17+
authenticationFailure: () => void;
18+
19+
/**
20+
* This event gets called when LightDM wants to display an error message or when an error occurs in the Authenticator class.
21+
* @param message The error message.
22+
*/
23+
errorMessage: (message: string) => void;
24+
25+
/**
26+
* This event gets called when LightDM wants to display an info message.
27+
* @param message The info message.
28+
*/
29+
infoMessage: (message: string) => void;
1630
}
1731

1832
export class Authenticator {
19-
private _loginElements: UILoginElements;
20-
private _lockScreenElements: UILockScreenElements;
21-
2233
private _authenticating: boolean = false;
2334
private _authenticated: boolean = false;
35+
36+
private _authEvents: AuthenticatorEvents | null = null;
37+
2438
private _username: string = "";
2539
private _password: string = "";
2640
private _session: string = "ubuntu"; // always start with ubuntu.desktop X11 session
2741

28-
private _isLockScreen: boolean = false;
29-
private _activeSession: LightDMUser | undefined;
42+
public static readonly MAX_LEN_USERNAME = 32;
43+
public static readonly MAX_LEN_PASSWORD = 128;
3044

3145
public constructor() {
32-
this._loginElements = {
33-
loginForm: document.getElementById('login-form') as HTMLFormElement,
34-
loginInput: document.getElementById('login') as HTMLInputElement,
35-
passwordInput: document.getElementById('password') as HTMLInputElement,
36-
loginButton: document.getElementById('login-button') as HTMLButtonElement,
37-
};
38-
39-
this._lockScreenElements = {
40-
lockForm: document.getElementById('lock-form') as HTMLFormElement,
41-
displayName: document.getElementById('active-user-session-display-name') as HTMLHeadingElement,
42-
loginName: document.getElementById('active-user-session-login-name') as HTMLHeadingElement,
43-
passwordInput: document.getElementById('active-user-session-password') as HTMLInputElement,
44-
unlockButton: document.getElementById('unlock-button') as HTMLButtonElement,
45-
};
46-
47-
// Check for any active sessions
48-
this._activeSession = lightdm.users.find((user: LightDMUser) => user.logged_in);
49-
50-
if (this._activeSession !== undefined) {
51-
// Active session found, show lock screen form
52-
this._isLockScreen = true;
53-
this._username = this._activeSession.username;
54-
this._initLockScreenForm();
55-
}
56-
else {
57-
// No active session found, show login form
58-
this._initLoginForm();
59-
}
60-
6146
// Initialize LightDM event listeners
6247
this._initLightDMListeners();
6348
}
6449

65-
private _initLoginForm(): void {
66-
// This event gets called when the user clicks the login button or submits the login form in any other way
67-
this._loginElements.loginForm.addEventListener('submit', (event: Event) => {
68-
event.preventDefault();
69-
this._username = this._loginElements.loginInput.value.trim();
70-
this._password = this._loginElements.passwordInput.value.trim();
71-
this._login();
72-
});
73-
74-
// Display the login form
75-
this._loginElements.loginForm.style.display = "block";
76-
this._loginElements.loginInput.focus();
77-
}
78-
79-
private _initLockScreenForm(): void {
80-
// Populate lock screen data
81-
this._lockScreenElements.displayName.innerText = this._activeSession?.display_name ?? this._activeSession?.username ?? "User";
82-
this._lockScreenElements.loginName.innerText = this._activeSession?.username ?? "user";
83-
84-
// This event gets called when the user clicks the unlock button or submits the lock screen form in any other way
85-
this._lockScreenElements.lockForm.addEventListener('submit', (event: Event) => {
86-
event.preventDefault();
87-
this._password = this._lockScreenElements.passwordInput.value.trim();
88-
this._login();
89-
});
90-
91-
// Display the lock screen form
92-
this._lockScreenElements.lockForm.style.display = "block";
93-
this._lockScreenElements.passwordInput.focus();
94-
}
95-
9650
private _initLightDMListeners(): void {
9751
// This event gets called when LightDM asks for more authentication data
9852
lightdm.show_prompt.connect((message: string, type: LightDMPromptType) => {
@@ -122,9 +76,15 @@ export class Authenticator {
12276
switch (type) {
12377
case LightDMMessageType.Info:
12478
console.log(`LightDM info message: ${message}`);
79+
if (this._authEvents) {
80+
this._authEvents.infoMessage(message);
81+
}
12582
break;
12683
case LightDMMessageType.Error:
12784
console.error(`LightDM error message: ${message}`);
85+
if (this._authEvents) {
86+
this._authEvents.errorMessage(message);
87+
}
12888
break;
12989
default:
13090
console.warn(`Unknown lightDM message type: ${type}, message: ${message}`);
@@ -144,96 +104,77 @@ export class Authenticator {
144104
if (lightdm.is_authenticated) {
145105
this._authenticated = true;
146106
console.log("LightDM authentication successful! Starting session...");
107+
if (this._authEvents) {
108+
this._authEvents.authenticationComplete();
109+
}
147110
lightdm.start_session(this._session ?? null);
148111
}
149112
else {
150113
console.log("LightDM authentication failed. User not found or password incorrect.");
151114
this._stopAuthentication();
152-
this._wigglePasswordInput();
115+
if (this._authEvents) {
116+
this._authEvents.authenticationFailure();
117+
}
153118
}
154119
}
155120
catch (err) {
156121
console.error(err);
122+
if (this._authEvents) {
123+
this._authEvents.errorMessage(String(err));
124+
}
157125
}
158126
});
159127
}
160128

161-
private _clearAuth(): void {
162-
if (!this._isLockScreen) {
163-
this._username = "";
164-
}
165-
this._password = "";
129+
/**
130+
* Check if the authentication process has started.
131+
* @returns True if the authentication process has started, false otherwise.
132+
*/
133+
public get authenticating(): boolean {
134+
return this._authenticating;
166135
}
167136

168-
private _disableForm(): void {
169-
const uiElementsObject = this._isLockScreen ? this._lockScreenElements : this._loginElements;
170-
for (const element of Object.values(uiElementsObject)) {
171-
if ("disabled" in element && typeof element.disabled === "boolean") { // check if element has disabled property and disable every element that has it
172-
element.disabled = true;
173-
}
174-
}
175-
176-
// Unfocus the focused element
177-
if (document.activeElement) {
178-
(document.activeElement as HTMLElement).blur();
179-
}
137+
/**
138+
* Check if the authentication process has completed.
139+
* @returns True if the authentication process has completed, false otherwise.
140+
*/
141+
public get authenticated(): boolean {
142+
return this._authenticated;
180143
}
181144

182-
private _enableForm(focusElement: HTMLInputElement | null = null): void {
183-
const uiElementsObject = this._isLockScreen ? this._lockScreenElements : this._loginElements;
184-
for (const element of Object.values(uiElementsObject)) {
185-
if ("disabled" in element && typeof element.disabled === "boolean") { // check if element has disabled property and enable every element that has it
186-
element.disabled = false;
187-
}
188-
}
189-
190-
if (!focusElement) {
191-
focusElement = this._getInputToFocusOn();
192-
}
193-
focusElement.focus();
145+
/**
146+
* Get the username that is currently being authenticated.
147+
* @returns The username that is currently being authenticated.
148+
*/
149+
public get username(): string {
150+
return this._username;
194151
}
195152

196-
private _wigglePasswordInput(clearInput: boolean = true): void {
197-
const passwordInput = this._isLockScreen ? this._lockScreenElements.passwordInput : this._loginElements.passwordInput;
198-
passwordInput.classList.add('wiggle');
199-
setTimeout(() => {
200-
passwordInput.classList.remove('wiggle');
201-
}, 800); // overdo the animation a bit to make sure it's finished before we remove the class
202-
203-
if (clearInput) {
204-
passwordInput.value = "";
205-
passwordInput.focus();
206-
}
153+
/**
154+
* Configure the callback functions that are called on certain events.
155+
* @param authEvents The callback functions that are called on certain events.
156+
* @returns void
157+
*/
158+
public set authEvents(authEvents: AuthenticatorEvents) {
159+
this._authEvents = authEvents;
207160
}
208161

209-
private _getInputToFocusOn(): HTMLInputElement {
210-
if (this._isLockScreen) {
211-
return this._lockScreenElements.passwordInput;
212-
}
213-
else {
214-
if (this._loginElements.loginInput.value.trim() === "") {
215-
return this._loginElements.loginInput;
216-
}
217-
return this._loginElements.passwordInput;
218-
}
162+
private _clearAuth(): void {
163+
this._username = "";
164+
this._password = "";
219165
}
220166

221167
private _stopAuthentication(): void {
222168
lightdm.cancel_authentication();
223169
this._authenticating = false;
224170
this._authenticated = false;
225171
this._clearAuth();
226-
this._enableForm();
227172
}
228173

229174
private _startAuthentication(): void {
230175
try {
231176
console.log("Starting LightDM authentication...");
232177
lightdm.cancel_authentication();
233-
if (this._username === "" || this._password === "") {
234-
console.log("Username or password is empty. Stopping authentication.");
235-
return this._stopAuthentication();
236-
}
237178
this._authenticating = true;
238179
lightdm.authenticate(this._username); // provide username to skip the username prompt
239180
}
@@ -242,12 +183,29 @@ export class Authenticator {
242183
}
243184
}
244185

245-
private _login(): void {
186+
/**
187+
* Start the login process. The authenticationStart auth event will be called when the login process starts without error.
188+
* @param username The username to log in with.
189+
* @param password The password to log in with.
190+
* @returns void
191+
*/
192+
public login(username: string, password: string): void {
193+
this._username = username.substring(0, Authenticator.MAX_LEN_USERNAME).trim();
194+
this._password = password.substring(0, Authenticator.MAX_LEN_PASSWORD); // do not trim password as it could contain spaces at the beginning or end
195+
246196
if (this._authenticating || this._authenticated) {
197+
console.warn("Login() was called while already authenticating or authenticated. Stopping authentication.");
247198
return;
248199
}
249200

250-
this._disableForm();
201+
if (this._username === "" || this._password === "") {
202+
console.log("Login() was called while username or password is empty. Stopping authentication.");
203+
return;
204+
}
205+
206+
if (this._authEvents) {
207+
this._authEvents.authenticationStart();
208+
}
251209
this._startAuthentication();
252210
}
253211
}

scripts/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Import local classes
22
import { Data } from './data';
3-
import { UI } from './ui';
3+
import { UI } from './ui/ui';
44
import { Authenticator } from './auth';
55

66
declare global {
77
interface Window {
88
data: Data;
9-
ui: UI;
109
auth: Authenticator;
10+
ui: UI;
1111

1212
sleep(ms: number): Promise<void>;
1313
}
@@ -24,8 +24,8 @@ window.sleep = sleep;
2424
async function initGreeter(): Promise<void> {
2525
// Initialize local classes
2626
window.data = new Data();
27-
window.ui = new UI();
2827
window.auth = new Authenticator();
28+
window.ui = new UI(window.auth);
2929
}
3030

3131
window.addEventListener("GreeterReady", () => {

scripts/ui.ts renamed to scripts/ui/infobars.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface UIInfoElements {
66
debug: HTMLSpanElement;
77
}
88

9-
export class UI {
9+
export class InfoBarsUI {
1010
private _infoElements: UIInfoElements;
1111

1212
public constructor() {
@@ -25,11 +25,11 @@ export class UI {
2525
// Populate debug info
2626
this._infoElements.debug.innerText = '';
2727
window.addEventListener('error', (event: ErrorEvent) => {
28-
window.ui._infoElements.debug.innerText += event.error + '\n';
28+
this._infoElements.debug.innerText += event.error + '\n';
2929
});
3030

3131
// Populate version info
32-
this._infoElements.version.innerText = window.data.pkgName + " " + window.data.pkgVersion;
32+
this._infoElements.version.innerText = window.data.pkgName + " v" + window.data.pkgVersion;
3333

3434
// Populate hostname info
3535
this._infoElements.hostname.innerText = window.data.hostname;

0 commit comments

Comments
 (0)