forked from tools/josh
1
0
Fork 0

Move current user to environment variable

This commit is contained in:
Florine W. Dekker 2019-11-06 12:53:48 +01:00
parent 540a4f4ebe
commit 8e9747c5fe
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
5 changed files with 201 additions and 248 deletions

View File

@ -4,7 +4,7 @@ import {Directory, File, FileSystem, Path} from "./FileSystem"
import {IllegalStateError} from "./Shared";
import {Environment, InputArgs} from "./Shell";
import {EscapeCharacters} from "./Terminal";
import {UserSession} from "./UserSession";
import {UserList} from "./UserList";
/**
@ -18,7 +18,7 @@ export class Commands {
/**
* The user session describing the user that executes commands.
*/
private readonly userSession: UserSession;
private readonly userSession: UserList;
/**
* The file system to interact with.
*/
@ -36,7 +36,7 @@ export class Commands {
* @param userSession the user session describing the user that executes commands
* @param fileSystem the file system to interact with
*/
constructor(environment: Environment, userSession: UserSession, fileSystem: FileSystem) {
constructor(environment: Environment, userSession: UserList, fileSystem: FileSystem) {
this.environment = environment;
this.userSession = userSession;
this.fileSystem = fileSystem;
@ -225,8 +225,6 @@ export class Commands {
execute(input: InputArgs): string {
if (input.command === "factory-reset") {
Cookies.remove("files");
Cookies.remove("cwd");
Cookies.remove("user");
Cookies.remove("env");
location.reload();
throw new Error("Goodbye");
@ -324,7 +322,7 @@ export class Commands {
}
private exit(): string {
this.userSession.logOut();
this.environment["user"].value = "";
return "";
}
@ -471,8 +469,8 @@ export class Commands {
}
private poweroff(): string {
const user = this.userSession.currentUser;
if (user === undefined)
const userName = this.environment["user"].value;
if (userName === "")
throw new IllegalStateError("Cannot execute `poweroff` while not logged in.");
Cookies.set("poweroff", "true", {
@ -484,7 +482,7 @@ export class Commands {
return "" +
`Shutdown NOW!
*** FINAL System shutdown message from ${user.name}@fwdekker.com ***
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
System going down IMMEDIATELY
@ -563,7 +561,7 @@ export class Commands {
}
private whoami(): string {
const user = this.userSession.currentUser;
const user = this.userSession.get(this.environment["user"].value);
if (user === undefined)
throw new IllegalStateError("Cannot execute `whoami` while not logged in.");

View File

@ -28,6 +28,6 @@ addOnLoad(() => {
window.execute = (command: string) => window.terminal.processInput(command);
// @ts-ignore: Ugly hack to execute it anyway
if (window.terminal.shell.userSession.isLoggedIn)
if (window.terminal.shell.environment["user"].value !== "")
window.terminal.processInput("ls");
});

View File

@ -3,7 +3,7 @@ import {Commands} from "./Commands";
import {Directory, File, FileSystem, Node, Path} from "./FileSystem";
import {asciiHeaderHtml, IllegalStateError, stripHtmlTags} from "./Shared";
import {EscapeCharacters, InputHistory} from "./Terminal";
import {UserSession} from "./UserSession";
import {UserList} from "./UserList";
/**
@ -21,7 +21,7 @@ export class Shell {
/**
* The user session describing the user that interacts with the shell.
*/
private readonly userSession: UserSession;
private readonly userList: UserList;
/**
* The file system.
*/
@ -45,46 +45,12 @@ export class Shell {
*/
constructor(inputHistory: InputHistory) {
this.inputHistory = inputHistory;
this.userList = new UserList();
this.fileSystem = Shell.loadFileSystem();
this.environment = Shell.loadEnvironment(this.fileSystem, this.userList);
this.commands = new Commands(this.environment, this.userList, this.fileSystem);
// Read user from cookie
const user = Cookies.get("user");
if (user === "")
this.userSession = new UserSession();
else if (user === undefined || !UserSession.userExists(user))
this.userSession = new UserSession("felix");
else
this.userSession = new UserSession(user);
// Read files from cookie
let files: Directory | undefined = undefined;
const filesJson = Cookies.get("files");
if (filesJson !== undefined) {
try {
const parsedFiles = Node.deserialize(filesJson);
if (parsedFiles instanceof Directory)
files = parsedFiles;
else
console.warn("`files` cookie contains non-directory.");
} catch (error) {
console.warn("Failed to deserialize `files` cookie.", error);
}
}
this.fileSystem = new FileSystem(files);
// Read environment from cookie
const environment = Cookies.get("env") || "{}";
try {
this.environment = JSON.parse(environment);
} catch (error) {
console.warn("Failed to set environment from cookie.");
this.environment = {};
}
// Check cwd in environment
if (this.environment["cwd"] === undefined || !this.fileSystem.has(new Path(this.environment["cwd"].value)))
this.environment["cwd"] = {value: "/", readonly: true};
this.commands = new Commands(this.environment, this.userSession, this.fileSystem);
this.saveState();
}
@ -94,7 +60,7 @@ export class Shell {
* @return the header that is displayed when a user logs in
*/
generateHeader(): string {
if (!this.userSession.isLoggedIn)
if (this.environment["user"].value === "")
return "";
return "" +
@ -115,30 +81,28 @@ export class Shell {
* @return the prefix based on the current state of the terminal
*/
generatePrefix(): string {
if (!this.userSession.isLoggedIn) {
const userName = this.environment["user"].value;
if (userName === "") {
if (this.attemptUser === undefined)
return "login as: ";
else
return `Password for ${this.attemptUser}@fwdekker.com: `;
} else {
if (this.userSession.currentUser === undefined)
throw new IllegalStateError("User is logged in as undefined.");
const cwd = new Path(this.environment["cwd"].value);
const parts = cwd.ancestors.reverse();
parts.push(cwd);
const link = parts
.map(part => {
const node = this.fileSystem.get(part);
if (node === undefined)
throw new IllegalStateError(`Ancestor '${part}' of cwd does not exist.`);
return node.nameString(part.fileName + "/", part);
})
.join("");
return `${this.userSession.currentUser.name}@fwdekker.com <span class="prefixPath">${link}</span>&gt; `;
}
const cwd = new Path(this.environment["cwd"].value);
const parts = cwd.ancestors.reverse();
parts.push(cwd);
const link = parts
.map(part => {
const node = this.fileSystem.get(part);
if (node === undefined)
throw new IllegalStateError(`Ancestor '${part}' of cwd does not exist.`);
return node.nameString(part.fileName + "/", part);
})
.join("");
return `${userName}@fwdekker.com <span class="prefixPath">${link}</span>&gt; `;
}
@ -148,19 +112,26 @@ export class Shell {
* @param inputString the input to process
*/
execute(inputString: string): string {
if (!this.userSession.isLoggedIn) {
if (this.environment["user"].value === "") {
if (this.attemptUser === undefined) {
this.attemptUser = inputString.trim();
this.attemptUser = inputString.trim() || undefined; // Set to undefined if empty string
this.saveState();
return EscapeCharacters.Escape + EscapeCharacters.HideInput;
} else {
const isLoggedIn = this.userSession.tryLogIn(this.attemptUser, inputString);
this.attemptUser = undefined;
const attemptUser = this.userList.get(this.attemptUser);
let resultString: string;
if (attemptUser !== undefined && attemptUser.password === inputString) {
this.environment["user"].value = this.attemptUser;
resultString = this.generateHeader();
} else {
resultString = "Access denied\n";
}
this.attemptUser = undefined;
this.saveState();
return EscapeCharacters.Escape + EscapeCharacters.ShowInput
+ (isLoggedIn ? this.generateHeader() : "Access denied\n");
return EscapeCharacters.Escape + EscapeCharacters.ShowInput + resultString;
}
}
@ -183,11 +154,12 @@ export class Shell {
output = this.writeToFile(path, output, input.redirectTarget[0] === "append");
}
if (!this.userSession.isLoggedIn) {
if (this.environment["user"].value === "") {
this.inputHistory.clear();
for (const key of Object.getOwnPropertyNames(this.environment))
delete this.environment[key];
this.environment["cwd"] = {value: "/", readonly: true};
this.environment["user"] = {value: "", readonly: true};
}
this.saveState();
@ -231,9 +203,70 @@ export class Shell {
"path": "/"
});
Cookies.set("env", this.environment, {"path": "/"});
}
const user = this.userSession.currentUser;
Cookies.set("user", user === undefined ? "" : user.name, {"path": "/"});
/**
* Returns the file system loaded from a cookie, or the default file system if no cookie is present or the cookie
* is invalid.
*
* @return the file system loaded from a cookie, or the default file system if no cookie is present or the cookie
* is invalid
*/
private static loadFileSystem(): FileSystem {
let files: Directory | undefined = undefined;
const filesString = Cookies.get("files");
if (filesString !== undefined) {
try {
const parsedFiles = Node.deserialize(filesString);
if (parsedFiles instanceof Directory)
files = parsedFiles;
else
console.warn("`files` cookie contains non-directory.");
} catch (error) {
console.warn("Failed to deserialize `files` cookie.", error);
}
}
return new FileSystem(files);
}
/**
* Returns the environment loaded from a cookie, or the default environment if no cookie is present or the cookie
* is invalid.
*
* @param fileSystem the file system used to validate the `cwd` environment variable
* @param userList the list of users used to validate the `user` environment variable
* @return the environment loaded from a cookie, or the default environment if no cookie is present or the cookie
* is invalid
*/
private static loadEnvironment(fileSystem: FileSystem, userList: UserList): Environment {
const environmentString = Cookies.get("env") || "{}";
let environment: Environment;
try {
environment = JSON.parse(environmentString);
} catch (error) {
console.warn("Failed to set environment from cookie.");
environment = {};
}
// Check cwd in environment
if (environment["cwd"] === undefined) {
environment["cwd"] = {value: "/", readonly: true};
} else if (!fileSystem.has(new Path(environment["cwd"].value))) {
console.warn(`Invalid cwd '${environment["cwd"].value}' in environment.`);
environment["cwd"] = {value: "/", readonly: true};
}
environment["cwd"].readonly = true;
// Check user in environment
if (environment["user"] === undefined) {
environment["user"] = {value: "felix", readonly: true};
} else if (environment["user"].value !== "" && !userList.has(environment["user"].value)) {
console.warn(`Invalid user '${environment["cwd"].value}' in environment.`);
environment["user"] = {value: "felix", readonly: true};
}
environment["user"].readonly = true;
return environment;
}
}

89
src/main/js/UserList.ts Normal file
View File

@ -0,0 +1,89 @@
/**
* Manages a user session.
*/
export class UserList {
/**
* All users that exist in the system.
*/
private _users: User[];
/**
* Constructs a new list of users.
*
* @param users the list of users that are available, or `undefined` if the default users should be available
*/
constructor(users: User[] | undefined = undefined) {
if (users === undefined)
this._users = [
new User("felix", "password", "Why are you logged in on <i>my</i> account?"),
new User("root", "root", "Wait how did you get here?")
];
else
this._users = users;
}
/**
* Returns a copy of the list of all users.
*
* @return a copy of the list of all users
*/
get users(): User[] {
return this._users.slice();
}
/**
* Returns `true` if and only if a user with the given name exists.
*
* @param name the name of the user to check
* @return `true` if and only if a user with the given name exists
*/
has(name: string): boolean {
return this.get(name) !== undefined;
}
/**
* Returns the user with the given name, or `undefined` if there is no such user.
*
* @param name the name of the user to return
* @return the user with the given name, or `undefined` if there is no such user
*/
get(name: string): User | undefined {
return this._users.find(it => it.name === name);
}
}
/**
* A user that can be logged in to.
*/
export class User {
/**
* The name of the user.
*/
readonly name: string;
/**
* The password of the user.
*/
readonly password: string;
/**
* The description of the user.
*/
readonly description: string;
/**
* Constructs a new user.
*
* @param name the name of the user
* @param password the password of the user
* @param description the description of the user
*/
constructor(name: string, password: string, description: string) {
this.name = name;
this.password = password;
this.description = description;
}
}

View File

@ -1,167 +0,0 @@
import {IllegalStateError} from "./Shared";
/**
* Manages a user session.
*/
export class UserSession {
/**
* All users that exist in the system.
*/
private static _users: User[];
/**
* The user that is currently logged in in this session, or `undefined` if no user is logged in.
*/
private _currentUser: User | undefined;
/**
* Constructs a new user.
*
* @param name the name of the user to be logged in as by default, or `undefined` if no user should be logged in at
* the start of the session
* @throws if a name is given and no user with that name exists
*/
constructor(name: string | undefined = undefined) {
if (name !== undefined) {
const user = UserSession.getUser(name);
if (user === undefined)
throw new Error(`Could not find user \`${name}\`.`);
this._currentUser = user;
}
}
/**
* Returns a copy of the list of all users.
*
* @return a copy of the list of all users
*/
static get users(): User[] {
return UserSession._users.slice();
}
/**
* Returns the user that is currently logged in in this session, or `undefined` if no user is logged in.
*
* @return the user that is currently logged in in this session, or `undefined` if no user is logged in
*/
get currentUser(): User | undefined {
return this._currentUser;
}
/**
* Returns `true` if and only if a user is currently logged in.
*
* @return `true` if and only if a user is currently logged in
*/
get isLoggedIn(): boolean {
return this.currentUser !== undefined;
}
/**
* Returns `true` if and only if a user with the given name exists.
*
* @param name the name of the user to check
* @return `true` if and only if a user with the given name exists
*/
static userExists(name: string): boolean {
return this.getUser(name) !== undefined;
}
/**
* Returns the user with the given name, or `undefined` if there is no such user.
*
* @param name the name of the user to return
* @return the user with the given name, or `undefined` if there is no such user
*/
static getUser(name: string): User | undefined {
return UserSession._users.find(it => it.name === name);
}
/**
* Attempts to log in as the given user with the given password, and returns `true` if and only if the user was
* successfully logged in.
*
* If logging in was successful, the current user is updated to the user with the given name.
*
* @param name the name of the user to try to log in as
* @param password the password of the user to try to log in as
* @return `true` if and only if the user was successfully logged in
* @throws if a user is already logged in
*/
tryLogIn(name: string, password: string): boolean {
if (this.isLoggedIn)
throw new IllegalStateError("Cannot try to log in while already logged in.");
const user = UserSession.getUser(name);
if (user === undefined)
return false;
if (user.password !== password)
return false;
this._currentUser = user;
return true;
}
/**
* Logs out the current user.
*/
logOut(): void {
this._currentUser = undefined;
}
/**
* Initializes the array of users to the default array.
*
* @private
*/
static _initialize(): void {
this._users = [
new User("felix", "password", "Why are you logged in on <i>my</i> account?"),
new User("root", "root", "Wait how did you get here?")
];
}
}
/**
* A user that can be logged in to.
*/
export class User {
/**
* The name of the user.
*/
readonly name: string;
/**
* The password of the user.
*/
readonly password: string;
/**
* The description of the user.
*/
readonly description: string;
/**
* Constructs a new user.
*
* @param name the name of the user
* @param password the password of the user
* @param description the description of the user
*/
constructor(name: string, password: string, description: string) {
this.name = name;
this.password = password;
this.description = description;
}
}
// Initialize default list of users
UserSession._initialize();