forked from tools/josh
Move current user to environment variable
This commit is contained in:
parent
540a4f4ebe
commit
8e9747c5fe
|
@ -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.");
|
||||
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
|
@ -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>> `;
|
||||
}
|
||||
|
||||
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>> `;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
Loading…
Reference in New Issue