Be able to authenticate and get back Success, PasswordReset, and Locked
This commit is contained in:
parent
84ee790f0b
commit
90224a6841
@ -141,7 +141,6 @@ impl Core {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
is_admin: user.admin,
|
is_admin: user.admin,
|
||||||
games: vec![],
|
|
||||||
})
|
})
|
||||||
.collect()),
|
.collect()),
|
||||||
Err(err) => fatal(err),
|
Err(err) => fatal(err),
|
||||||
@ -153,7 +152,6 @@ impl Core {
|
|||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
) -> ResultExt<Option<UserOverview>, AppError, FatalError> {
|
) -> ResultExt<Option<UserOverview>, AppError, FatalError> {
|
||||||
let users = return_error!(self.list_users().await);
|
let users = return_error!(self.list_users().await);
|
||||||
let games = return_error!(self.list_games().await);
|
|
||||||
let user = match users.into_iter().find(|user| user.id == user_id) {
|
let user = match users.into_iter().find(|user| user.id == user_id) {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => return ok(None),
|
None => return ok(None),
|
||||||
@ -162,11 +160,6 @@ impl Core {
|
|||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
name: user.name,
|
name: user.name,
|
||||||
is_admin: user.is_admin,
|
is_admin: user.is_admin,
|
||||||
games: games
|
|
||||||
.into_iter()
|
|
||||||
.filter(|g| g.gm == user.id)
|
|
||||||
.map(|g| g.id)
|
|
||||||
.collect(),
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
mod game_management;
|
mod game_management;
|
||||||
mod user_management;
|
mod user_management;
|
||||||
|
mod types;
|
||||||
|
|
||||||
use axum::{http::StatusCode, Json};
|
use axum::{http::StatusCode, Json};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
pub use game_management::*;
|
pub use game_management::*;
|
||||||
pub use user_management::*;
|
pub use user_management::*;
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
use result_extended::ResultExt;
|
use result_extended::ResultExt;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Core,
|
core::Core,
|
||||||
types::{AppError, FatalError},
|
types::{AppError, FatalError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub struct HealthCheck {
|
|
||||||
pub ok: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn wrap_handler<F, A, Fut>(f: F) -> (StatusCode, Json<Option<A>>)
|
pub async fn wrap_handler<F, A, Fut>(f: F) -> (StatusCode, Json<Option<A>>)
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Fut,
|
F: FnOnce() -> Fut,
|
||||||
|
83
visions/server/src/handlers/types.rs
Normal file
83
visions/server/src/handlers/types.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use typeshare::typeshare;
|
||||||
|
|
||||||
|
use crate::database::UserId;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct HealthCheck {
|
||||||
|
pub ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", content = "content")]
|
||||||
|
#[typeshare]
|
||||||
|
pub enum AccountState {
|
||||||
|
Normal,
|
||||||
|
PasswordReset(String),
|
||||||
|
Locked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::types::AccountState> for AccountState {
|
||||||
|
fn from(s: crate::types::AccountState) -> Self {
|
||||||
|
match s {
|
||||||
|
crate::types::AccountState::Normal => Self::Normal,
|
||||||
|
crate::types::AccountState::PasswordReset(r) => {
|
||||||
|
Self::PasswordReset(format!("{}", r.format("%Y-%m-%d %H:%M:%S")))
|
||||||
|
}
|
||||||
|
crate::types::AccountState::Locked => Self::Locked,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[typeshare]
|
||||||
|
pub struct User {
|
||||||
|
pub id: UserId,
|
||||||
|
pub name: String,
|
||||||
|
pub password: String,
|
||||||
|
pub admin: bool,
|
||||||
|
pub state: AccountState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::types::User> for User {
|
||||||
|
fn from(u: crate::types::User) -> Self {
|
||||||
|
Self {
|
||||||
|
id: u.id,
|
||||||
|
name: u.name,
|
||||||
|
password: u.password,
|
||||||
|
admin: u.admin,
|
||||||
|
state: AccountState::from(u.state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[typeshare]
|
||||||
|
pub struct UserOverview {
|
||||||
|
pub id: UserId,
|
||||||
|
pub name: String,
|
||||||
|
pub is_admin: bool,
|
||||||
|
pub games: Vec<crate::types::GameOverview>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserOverview {
|
||||||
|
pub fn new(user: crate::types::UserOverview, games: Vec<crate::types::GameOverview>) -> Self {
|
||||||
|
let s = Self::from(user);
|
||||||
|
Self{ games, ..s }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::types::UserOverview> for UserOverview {
|
||||||
|
fn from(input: crate::types::UserOverview) -> Self {
|
||||||
|
Self {
|
||||||
|
id: input.id,
|
||||||
|
name: input.name,
|
||||||
|
is_admin: input.is_admin,
|
||||||
|
games: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -7,7 +7,7 @@ use typeshare::typeshare;
|
|||||||
use crate::{
|
use crate::{
|
||||||
core::{AuthResponse, Core},
|
core::{AuthResponse, Core},
|
||||||
database::{SessionId, UserId},
|
database::{SessionId, UserId},
|
||||||
types::{AppError, FatalError, User, UserOverview},
|
types::{AppError, FatalError, User},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -106,20 +106,19 @@ pub async fn get_user(
|
|||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
user_id: Option<UserId>,
|
user_id: Option<UserId>,
|
||||||
) -> ResultExt<Option<UserOverview>, AppError, FatalError> {
|
) -> ResultExt<Option<crate::types::UserOverview>, AppError, FatalError> {
|
||||||
auth_required(core.clone(), headers, |user| async move {
|
auth_required(core.clone(), headers, |user| async move {
|
||||||
match user_id {
|
match user_id {
|
||||||
Some(user_id) => core.user(user_id).await,
|
Some(user_id) => core.user(user_id).await,
|
||||||
None => core.user(user.id).await,
|
None => core.user(user.id).await,
|
||||||
}
|
}
|
||||||
})
|
}).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_users(
|
pub async fn get_users(
|
||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> ResultExt<Vec<UserOverview>, AppError, FatalError> {
|
) -> ResultExt<Vec<crate::types::UserOverview>, AppError, FatalError> {
|
||||||
auth_required(core.clone(), headers, |_user| async move {
|
auth_required(core.clone(), headers, |_user| async move {
|
||||||
core.list_users().await
|
core.list_users().await
|
||||||
})
|
})
|
||||||
|
@ -7,9 +7,7 @@ use axum::{
|
|||||||
routing::{get, post, put},
|
routing::{get, post, put},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use typeshare::typeshare;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Core,
|
core::Core,
|
||||||
@ -20,50 +18,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "type", content = "content")]
|
|
||||||
#[typeshare]
|
|
||||||
pub enum AccountState {
|
|
||||||
Normal,
|
|
||||||
PasswordReset(String),
|
|
||||||
Locked,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::types::AccountState> for AccountState {
|
|
||||||
fn from(s: crate::types::AccountState) -> Self {
|
|
||||||
match s {
|
|
||||||
crate::types::AccountState::Normal => Self::Normal,
|
|
||||||
crate::types::AccountState::PasswordReset(r) => {
|
|
||||||
Self::PasswordReset(format!("{}", r.format("%Y-%m-%d %H:%M:%S")))
|
|
||||||
}
|
|
||||||
crate::types::AccountState::Locked => Self::Locked,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[typeshare]
|
|
||||||
pub struct User {
|
|
||||||
pub id: UserId,
|
|
||||||
pub name: String,
|
|
||||||
pub password: String,
|
|
||||||
pub admin: bool,
|
|
||||||
pub state: AccountState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::types::User> for User {
|
|
||||||
fn from(u: crate::types::User) -> Self {
|
|
||||||
Self {
|
|
||||||
id: u.id,
|
|
||||||
name: u.name,
|
|
||||||
password: u.password,
|
|
||||||
admin: u.admin,
|
|
||||||
state: AccountState::from(u.state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn routes(core: Core) -> Router {
|
pub fn routes(core: Core) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route(
|
.route(
|
||||||
|
@ -167,16 +167,17 @@ pub enum Message {
|
|||||||
UpdateTabletop(Tabletop),
|
UpdateTabletop(Tabletop),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
pub struct UserOverview {
|
pub struct UserOverview {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
pub games: Vec<GameId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
pub struct GameOverview {
|
pub struct GameOverview {
|
||||||
pub id: GameId,
|
pub id: GameId,
|
||||||
|
@ -32,8 +32,8 @@ interface AuthedViewProps {
|
|||||||
const AuthedView = ({ client, children }: PropsWithChildren<AuthedViewProps>) => {
|
const AuthedView = ({ client, children }: PropsWithChildren<AuthedViewProps>) => {
|
||||||
const [state, manager] = useContext(StateContext)
|
const [state, manager] = useContext(StateContext)
|
||||||
return (
|
return (
|
||||||
<Authentication onAdminPassword={(password) => {
|
<Authentication onSetPassword={(password1, password2) => {
|
||||||
manager.setAdminPassword(password)
|
manager.setPassword(password1, password2)
|
||||||
}} onAuth={(username, password) => manager.auth(username, password)}>
|
}} onAuth={(username, password) => manager.auth(username, password)}>
|
||||||
{children}
|
{children}
|
||||||
</Authentication>
|
</Authentication>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SessionId, UserId, UserProfile } from "visions-types";
|
import { AuthResponse, SessionId, UserId, UserOverview } from "visions-types";
|
||||||
|
|
||||||
export type PlayingField = {
|
export type PlayingField = {
|
||||||
backgroundImage: string;
|
backgroundImage: string;
|
||||||
@ -63,14 +63,18 @@ export class Client {
|
|||||||
return fetch(url).then((response) => response.json());
|
return fetch(url).then((response) => response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAdminPassword(password: string) {
|
async setPassword(password_1: string, password_2: string) {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
url.pathname = `/api/v1/admin_password`;
|
url.pathname = `/api/v1/user/password`;
|
||||||
console.log("setting the admin password to: ", password);
|
|
||||||
return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify(password) });
|
return fetch(url, {
|
||||||
|
method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify({
|
||||||
|
password_1, password_2,
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async auth(username: string, password: string): Promise<SessionId | undefined> {
|
async auth(username: string, password: string): Promise<AuthResponse> {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
url.pathname = `/api/v1/auth`
|
url.pathname = `/api/v1/auth`
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@ -78,11 +82,10 @@ export class Client {
|
|||||||
headers: [['Content-Type', 'application/json']],
|
headers: [['Content-Type', 'application/json']],
|
||||||
body: JSON.stringify({ 'username': username, 'password': password })
|
body: JSON.stringify({ 'username': username, 'password': password })
|
||||||
});
|
});
|
||||||
const session_id: SessionId = await response.json();
|
return await response.json();
|
||||||
return session_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async profile(sessionId: SessionId, userId: UserId | undefined): Promise<UserProfile | undefined> {
|
async profile(sessionId: SessionId, userId: UserId | undefined): Promise<UserOverview | undefined> {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
if (userId) {
|
if (userId) {
|
||||||
url.pathname = `/api/v1/user${userId}`
|
url.pathname = `/api/v1/user${userId}`
|
||||||
|
@ -2,8 +2,8 @@ import { GameOverview } from "visions-types"
|
|||||||
import { CardElement } from '../Card/Card';
|
import { CardElement } from '../Card/Card';
|
||||||
|
|
||||||
|
|
||||||
export const GameOverviewElement = ({ game_type, game_name, gm, players }: GameOverview) => {
|
export const GameOverviewElement = ({ name, gm, players }: GameOverview) => {
|
||||||
return (<CardElement name={game_name}>
|
return (<CardElement name={name}>
|
||||||
<p><i>GM</i> {gm}</p>
|
<p><i>GM</i> {gm}</p>
|
||||||
<ul>
|
<ul>
|
||||||
{players.map((player) => player)}
|
{players.map((player) => player)}
|
||||||
|
@ -1,29 +1,21 @@
|
|||||||
import { UserProfile } from 'visions-types';
|
import { GameOverview, UserOverview } from 'visions-types';
|
||||||
import { CardElement, GameOverviewElement, UserManagementElement } from '..';
|
import { CardElement, GameOverviewElement, UserManagementElement } from '..';
|
||||||
import './Profile.css';
|
import './Profile.css';
|
||||||
|
|
||||||
interface ProfileProps {
|
interface ProfileProps {
|
||||||
profile: UserProfile,
|
profile: UserOverview,
|
||||||
users: UserProfile[],
|
games: GameOverview[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileElement = ({ profile, users }: ProfileProps) => {
|
export const ProfileElement = ({ profile, games }: ProfileProps) => {
|
||||||
const adminNote = profile.is_admin ? <div> <i>Note: this user is an admin</i> </div> : <></>;
|
const adminNote = profile.isAdmin ? <div> <i>Note: this user is an admin</i> </div> : <></>;
|
||||||
|
|
||||||
return (<div className="profile">
|
return (<div className="profile">
|
||||||
<CardElement name={profile.name}>
|
<CardElement name={profile.name}>
|
||||||
<div>Games: {profile.games.map((game) => {
|
<div>Games: {games.map((game) => {
|
||||||
return <span key={game.id}>{game.game_name} ({game.game_type})</span>;
|
return <span key={game.id}>{game.name} ({game.type})</span>;
|
||||||
}) }</div>
|
}) }</div>
|
||||||
{adminNote}
|
{adminNote}
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<div className="profile_columns">
|
|
||||||
<UserManagementElement users={users} />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{profile.games.map((game) => <GameOverviewElement {...game} />)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { UserProfile } from "visions-types"
|
import { UserOverview } from "visions-types"
|
||||||
import { CardElement } from ".."
|
import { CardElement } from ".."
|
||||||
|
|
||||||
interface UserManagementProps {
|
interface UserManagementProps {
|
||||||
users: UserProfile[]
|
users: UserOverview[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserManagementElement = ({ users }: UserManagementProps ) => {
|
export const UserManagementElement = ({ users }: UserManagementProps ) => {
|
||||||
|
@ -3,7 +3,7 @@ import { SessionId, Status, Tabletop } from "visions-types";
|
|||||||
import { Client } from "../../client";
|
import { Client } from "../../client";
|
||||||
import { assertNever } from "../../utils";
|
import { assertNever } from "../../utils";
|
||||||
|
|
||||||
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", sessionId: string };
|
type AuthState = { type: "Unauthed" } | { type: "Authed", sessionId: string } | { type: "PasswordReset", sessionId: string };
|
||||||
|
|
||||||
export enum LoadingState {
|
export enum LoadingState {
|
||||||
Loading,
|
Loading,
|
||||||
@ -21,7 +21,7 @@ type Action = { type: "SetAuthState", content: AuthState };
|
|||||||
const initialState = (): AppState => {
|
const initialState = (): AppState => {
|
||||||
let state: AppState = {
|
let state: AppState = {
|
||||||
state: LoadingState.Ready,
|
state: LoadingState.Ready,
|
||||||
auth: { type: "NoAdmin" },
|
auth: { type: "Unauthed" },
|
||||||
tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined },
|
tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +36,7 @@ const initialState = (): AppState => {
|
|||||||
const stateReducer = (state: AppState, action: Action): AppState => {
|
const stateReducer = (state: AppState, action: Action): AppState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "SetAuthState": {
|
case "SetAuthState": {
|
||||||
|
console.log("setReducer: ", action);
|
||||||
return { ...state, auth: action.content }
|
return { ...state, auth: action.content }
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -51,9 +52,9 @@ export const authState = (state: AppState): AuthState => state.auth
|
|||||||
|
|
||||||
export const getSessionId = (state: AppState): SessionId | undefined => {
|
export const getSessionId = (state: AppState): SessionId | undefined => {
|
||||||
switch (state.auth.type) {
|
switch (state.auth.type) {
|
||||||
case "NoAdmin": return undefined
|
|
||||||
case "Unauthed": return undefined
|
case "Unauthed": return undefined
|
||||||
case "Authed": return state.auth.sessionId
|
case "Authed": return state.auth.sessionId
|
||||||
|
case "PasswordReset": return state.auth.sessionId
|
||||||
default: {
|
default: {
|
||||||
assertNever(state.auth)
|
assertNever(state.auth)
|
||||||
return undefined
|
return undefined
|
||||||
@ -70,35 +71,38 @@ class StateManager {
|
|||||||
this.dispatch = dispatch;
|
this.dispatch = dispatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
async status() {
|
async setPassword(password1: string, password2: string) {
|
||||||
if (!this.client || !this.dispatch) return;
|
if (!this.client || !this.dispatch) return;
|
||||||
|
|
||||||
const { admin_enabled } = await this.client.health();
|
await this.client.setPassword(password1, password2);
|
||||||
if (!admin_enabled) {
|
|
||||||
this.dispatch({ type: "SetAuthState", content: { type: "NoAdmin" } });
|
|
||||||
}
|
|
||||||
|
|
||||||
return admin_enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setAdminPassword(password: string) {
|
|
||||||
if (!this.client || !this.dispatch) return;
|
|
||||||
|
|
||||||
await this.client.setAdminPassword(password);
|
|
||||||
let admin_enabled = await this.status();
|
|
||||||
if (admin_enabled) {
|
|
||||||
this.dispatch({ type: "SetAuthState", content: { type: "Unauthed" } });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async auth(username: string, password: string) {
|
async auth(username: string, password: string) {
|
||||||
if (!this.client || !this.dispatch) return;
|
if (!this.client || !this.dispatch) return;
|
||||||
|
|
||||||
let sessionId = await this.client.auth(username, password);
|
let authResponse = await this.client.auth(username, password);
|
||||||
|
switch (authResponse.type) {
|
||||||
|
case "Success": {
|
||||||
|
window.localStorage.setItem("sessionId", authResponse.content);
|
||||||
|
this.dispatch({ type: "SetAuthState", content: { type: "Authed", sessionId: authResponse.content } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "PasswordReset": {
|
||||||
|
window.localStorage.setItem("sessionId", authResponse.content);
|
||||||
|
this.dispatch({ type: "SetAuthState", content: { type: "PasswordReset", sessionId: authResponse.content } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Locked": {
|
||||||
|
this.dispatch({ type: "SetAuthState", content: { type: "Unauthed" } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
window.localStorage.setItem("sessionId", sessionId);
|
window.localStorage.setItem("sessionId", sessionId);
|
||||||
this.dispatch({ type: "SetAuthState", content: { type: "Authed", sessionId } });
|
this.dispatch({ type: "SetAuthState", content: { type: "Authed", sessionId } });
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,10 +115,6 @@ export const StateProvider = ({ client, children }: PropsWithChildren<StateProvi
|
|||||||
|
|
||||||
const stateManager = useRef(new StateManager(client, dispatch));
|
const stateManager = useRef(new StateManager(client, dispatch));
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
stateManager.current.status();
|
|
||||||
}, [stateManager]);
|
|
||||||
|
|
||||||
return <StateContext.Provider value={[state, stateManager.current]}>
|
return <StateContext.Provider value={[state, stateManager.current]}>
|
||||||
{children}
|
{children}
|
||||||
</StateContext.Provider>;
|
</StateContext.Provider>;
|
||||||
|
@ -7,10 +7,27 @@ interface UserRowProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UserRow = ({ user }: UserRowProps) => {
|
const UserRow = ({ user }: UserRowProps) => {
|
||||||
|
let accountState = "Normal";
|
||||||
|
|
||||||
|
switch (user.state.type) {
|
||||||
|
case "Normal": {
|
||||||
|
accountState = "Normal";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "PasswordReset": {
|
||||||
|
accountState = `PasswordReset until ${user.state.content}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Locked": {
|
||||||
|
accountState = "Locked";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (<tr>
|
return (<tr>
|
||||||
<td> {user.name} </td>
|
<td> {user.name} </td>
|
||||||
<td> {user.admin && "admin"} </td>
|
<td> {user.admin && "admin"} </td>
|
||||||
<td> {user.enabled && "enabled"} </td>
|
<td> {accountState} </td>
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,43 +4,36 @@ import { assertNever } from '../../utils';
|
|||||||
import './Authentication.css';
|
import './Authentication.css';
|
||||||
|
|
||||||
interface AuthenticationProps {
|
interface AuthenticationProps {
|
||||||
onAdminPassword: (password: string) => void;
|
onSetPassword: (password1: string, password2: string) => void;
|
||||||
onAuth: (username: string, password: string) => void;
|
onAuth: (username: string, password: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Authentication = ({ onAdminPassword, onAuth, children }: PropsWithChildren<AuthenticationProps>) => {
|
export const Authentication = ({ onSetPassword, onAuth, children }: PropsWithChildren<AuthenticationProps>) => {
|
||||||
// No admin password set: prompt for the admin password
|
// No admin password set: prompt for the admin password
|
||||||
// Password set, nobody logged in: prompt for login
|
// Password set, nobody logged in: prompt for login
|
||||||
// User logged in: show the children
|
// User logged in: show the children
|
||||||
|
|
||||||
let [userField, setUserField] = useState<string>("");
|
let [userField, setUserField] = useState<string>("");
|
||||||
let [pwField, setPwField] = useState<string>("");
|
let [pwField1, setPwField1] = useState<string>("");
|
||||||
|
let [pwField2, setPwField2] = useState<string>("");
|
||||||
let [state, _] = useContext(StateContext);
|
let [state, _] = useContext(StateContext);
|
||||||
|
|
||||||
|
console.log("Authentication component", state.state);
|
||||||
|
|
||||||
switch (state.state) {
|
switch (state.state) {
|
||||||
case LoadingState.Loading: {
|
case LoadingState.Loading: {
|
||||||
return <div>Loading</div>
|
return <div>Loading</div>
|
||||||
}
|
}
|
||||||
case LoadingState.Ready: {
|
case LoadingState.Ready: {
|
||||||
switch (state.auth.type) {
|
switch (state.auth.type) {
|
||||||
case "NoAdmin": {
|
|
||||||
return <div className="auth">
|
|
||||||
<div className="card">
|
|
||||||
<h1> Welcome to your new Visions VTT Instance </h1>
|
|
||||||
<p> Set your admin password: </p>
|
|
||||||
<input type="password" placeholder="Password" onChange={(evt) => setPwField(evt.target.value)} />
|
|
||||||
<input type="submit" value="Submit" onClick={() => onAdminPassword(pwField)} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
case "Unauthed": {
|
case "Unauthed": {
|
||||||
return <div className="auth card">
|
return <div className="auth card">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h1> Welcome to Visions VTT </h1>
|
<h1> Welcome to Visions VTT </h1>
|
||||||
<div className="auth__input-line">
|
<div className="auth__input-line">
|
||||||
<input type="text" placeholder="Username" onChange={(evt) => setUserField(evt.target.value)} />
|
<input type="text" placeholder="Username" onChange={(evt) => setUserField(evt.target.value)} />
|
||||||
<input type="password" placeholder="Password" onChange={(evt) => setPwField(evt.target.value)} />
|
<input type="password" placeholder="Password" onChange={(evt) => setPwField1(evt.target.value)} />
|
||||||
<input type="submit" value="Sign in" onClick={() => onAuth(userField, pwField)} />
|
<input type="submit" value="Sign in" onClick={() => onAuth(userField, pwField1)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
@ -48,6 +41,17 @@ export const Authentication = ({ onAdminPassword, onAuth, children }: PropsWithC
|
|||||||
case "Authed": {
|
case "Authed": {
|
||||||
return <div> {children} </div>;
|
return <div> {children} </div>;
|
||||||
}
|
}
|
||||||
|
case "PasswordReset": {
|
||||||
|
return <div className="auth">
|
||||||
|
<div className="card">
|
||||||
|
<h1> Password Reset </h1>
|
||||||
|
<p> Your password currently requires a reset. </p>
|
||||||
|
<input type="password" placeholder="Password" onChange={(evt) => setPwField1(evt.target.value)} />
|
||||||
|
<input type="password" placeholder="Retype your Password" onChange={(evt) => setPwField2(evt.target.value)} />
|
||||||
|
<input type="submit" value="Submit" onClick={() => onSetPassword(pwField1, pwField2)} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
assertNever(state.auth);
|
assertNever(state.auth);
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { UserProfile } from 'visions-types';
|
import { UserOverview } from 'visions-types';
|
||||||
import { Client } from '../../client';
|
import { Client } from '../../client';
|
||||||
import { ProfileElement } from '../../components';
|
import { ProfileElement } from '../../components';
|
||||||
import { getSessionId, StateContext, StateProvider } from '../../providers/StateProvider/StateProvider';
|
import { getSessionId, StateContext, StateProvider } from '../../providers/StateProvider/StateProvider';
|
||||||
@ -10,8 +10,8 @@ interface MainProps {
|
|||||||
|
|
||||||
export const MainView = ({ client }: MainProps) => {
|
export const MainView = ({ client }: MainProps) => {
|
||||||
const [state, _manager] = useContext(StateContext)
|
const [state, _manager] = useContext(StateContext)
|
||||||
const [profile, setProfile] = useState<UserProfile | undefined>(undefined)
|
const [profile, setProfile] = useState<UserOverview | undefined>(undefined)
|
||||||
const [users, setUsers] = useState<UserProfile[]>([])
|
const [users, setUsers] = useState<UserOverview[]>([])
|
||||||
|
|
||||||
const sessionId = getSessionId(state)
|
const sessionId = getSessionId(state)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -23,7 +23,7 @@ export const MainView = ({ client }: MainProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{profile && <ProfileElement profile={profile} users={[]} />}
|
{profile && <ProfileElement profile={profile} games={[]} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user