Skip to content
This repository has been archived by the owner on Dec 13, 2022. It is now read-only.

[Do not merge] User based access #3

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
137 changes: 116 additions & 21 deletions designer/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,135 @@ import ReactDOM from "react-dom";
import { LandingChoice, NewConfig, ChooseExisting } from "./pages/LandingPage";
import "./styles/index.scss";
import { initI18n } from "./i18n";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
useLocation,
} from "react-router-dom";
import Designer from "./designer";
import { SaveError } from "./pages/ErrorPages";
import { CookiesProvider, useCookies } from "react-cookie";
import { ProgressPlugin } from "webpack";

initI18n();

function NoMatch() {
return <div>404 Not found</div>;
}

function UserChoice() {
let [cookies, setcookie] = useCookies(["user"]);
let [userState, updateUserState] = React.useState(cookies.user);

let updateUser = (e: React.FormEvent<HTMLFormElement>) => {
setcookie("user", userState, {
path: "/",
sameSite: "strict",
});

return true;
};

// return (
// <form onSubmit={(e) => updateUser(e)}>
// <label htmlFor="user-picker">User select: </label>
// <input
// id="user-picker"
// name="user-picker"
// type="text"
// onChange={(e) => updateUserState(e.target.value)}
// value={userState}
// />
// <button type="submit">Submit</button>
// </form>
// );
return <div>Logged in as: {cookies.user}</div>;
}

function useQuery() {
const { search } = useLocation();

return React.useMemo(() => new URLSearchParams(search), [search]);
}

function AuthProvider({ children }) {
let [cookies, setCookie] = useCookies(["user"]);
let query = useQuery();

if (query.get("token")) {
setCookie("user", query.get("token"), {
path: "/",
sameSite: "strict",
});
}

if (cookies.user || query.get("token")) return children;

window.location.href = "/api/login";
}

function Auth() {
let [_, setcookie] = useCookies(["user"]);
let query = useQuery();

setcookie("user", query.get("token"), {
path: "/",
sameSite: "strict",
});
return <Redirect to="/" />;
}

function Logout() {
let [_, __, removeCookie] = useCookies(["user"]);

let doLogout = (e) => {
e.preventDefault();
removeCookie("user");
window.location.href = "/api/login";
};

return (
<a onClick={(e) => doLogout(e)} href="#">
Logout
</a>
);
}

export class App extends React.Component {
render() {
return (
<Router basename="/app">
<div id="app">
<Switch>
<Route path="/designer/:id" component={Designer} />
<Route path="/" exact>
<LandingChoice />
</Route>
<Route path="/new" exact>
<NewConfig />
</Route>
<Route path="/choose-existing" exact>
<ChooseExisting />
</Route>
<Route path="/save-error" exact>
<SaveError />
</Route>
<Route path="*">
<NoMatch />
</Route>
</Switch>
</div>
<CookiesProvider>
<div id="app">
<UserChoice />
<Logout />
<Switch>
<Route path="/auth" exact>
<Auth />
</Route>
<AuthProvider>
<Route path="/designer/:id" component={Designer} />
<Route path="/" exact>
<LandingChoice />
</Route>
<Route path="/new" exact>
<NewConfig />
</Route>
<Route path="/choose-existing" exact>
<ChooseExisting />
</Route>
<Route path="/save-error" exact>
<SaveError />
</Route>
<Route path="*">
<NoMatch />
</Route>
</AuthProvider>
</Switch>
</div>
</CookiesProvider>
</Router>
);
}
Expand Down
1 change: 1 addition & 0 deletions designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"lodash": "^4.17.21",
"moment-timezone": "^0.5.31",
"nanoid": "^3.1.12",
"react-cookie": "^4.1.1",
"react-helmet": "^6.1.0",
"react-router-dom": "^5.2.0",
"resolve": "^1.19.0",
Expand Down
3 changes: 3 additions & 0 deletions designer/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Config {
previewUrl: string;
publishUrl: string;
formsApiUrl: string;
managementUrl: string;
persistentBackend: "s3" | "blob" | "preview" | "api";
s3Bucket?: string;
logLevel: "trace" | "info" | "debug" | "error";
Expand Down Expand Up @@ -39,6 +40,7 @@ const schema = joi.object({
previewUrl: joi.string(),
publishUrl: joi.string(),
formsApiUrl: joi.string(),
managementUrl: joi.string(),
persistentBackend: joi
.string()
.valid("s3", "blob", "preview", "api")
Expand All @@ -63,6 +65,7 @@ const config = {
previewUrl: process.env.PREVIEW_URL || "http://localhost:3009",
publishUrl: process.env.PUBLISH_URL || "http://localhost:3009",
formsApiUrl: process.env.FORMS_API_URL || "http://localhost:4567",
managementUrl: process.env.MANAGEMENT_URL || "http://localhost:3030",
persistentBackend: process.env.PERSISTENT_BACKEND || "preview",
s3Bucket: process.env.S3_BUCKET,
logLevel: process.env.LOG_LEVEL || "error",
Expand Down
60 changes: 56 additions & 4 deletions designer/server/lib/persistence/apiPersistenceService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PersistenceService } from "./persistenceService";
import Wreck from "@hapi/wreck";
import config from "../../config";
import { FormConfiguration } from "@xgovformbuilder/model";

export class ApiPersistenceService implements PersistenceService {
logger: any;
Expand All @@ -12,22 +13,73 @@ export class ApiPersistenceService implements PersistenceService {
});
}

async uploadConfigurationForUser(
id: string,
configuration: string,
user: string
): Promise<any> {
return Wreck.post(`${config.formsApiUrl}/publish`, {
payload: JSON.stringify({ id, configuration: JSON.parse(configuration) }),
headers: {
"x-api-key": user,
},
});
}

async copyConfiguration(configurationId: string, newName: string) {
const configuration = await this.getConfiguration(configurationId);
return this.uploadConfiguration(newName, configuration);
}

async listAllConfigurations() {
const { payload } = await Wreck.get(`${config.formsApiUrl}/published`);
return JSON.parse(payload.toString());
async copyConfigurationForUser(
configurationId: string,
newName: string,
user: string
): Promise<any> {
const configuration = await this.getConfigurationForUser(
configurationId,
user
);
return this.uploadConfigurationForUser(newName, configuration, user);
}

async getConfiguration(id: string) {
console.log("Getting: ", id);
const { payload } = await Wreck.get(
`${config.formsApiUrl}/published/${id}`
);
var configuration = JSON.parse(payload.toString()).values;
return JSON.stringify(configuration);
}

async getConfigurationForUser(id: string, user: string): Promise<string> {
const { payload } = await Wreck.get(
`${config.formsApiUrl}/published/${id}`,
{
headers: {
"x-api-key": user,
},
}
);

var configuration = JSON.parse(payload.toString()).values;
return JSON.stringify(configuration);
}

async listAllConfigurations() {
const { payload } = await Wreck.get(`${config.formsApiUrl}/published`);
return JSON.parse(payload.toString());
}

async listAllConfigurationsForUser(
user: string
): Promise<FormConfiguration[]> {
console.log("Getting forms for: ", user);

const { payload } = await Wreck.get(`${config.formsApiUrl}/published`, {
headers: {
"x-api-key": user,
},
});
return JSON.parse(payload.toString());
}
}
13 changes: 13 additions & 0 deletions designer/server/lib/persistence/persistenceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ export interface PersistenceService {
getConfiguration(id: string): Promise<string>;
uploadConfiguration(id: string, configuration: string): Promise<any>;
copyConfiguration(configurationId: string, newName: string): Promise<any>;
listAllConfigurationsForUser(user: string): Promise<FormConfiguration[]>;
getConfigurationForUser(id: string, user: string): Promise<string>;
uploadConfigurationForUser(
id: string,
configuration: string,
user: string
): Promise<any>;
copyConfigurationForUser(
configurationId: string,
newName: string,
user: string
): Promise<any>;
}

export class StubPersistenceService implements PersistenceService {
Expand All @@ -21,6 +33,7 @@ export class StubPersistenceService implements PersistenceService {
getConfiguration(_id: string) {
return Promise.resolve("");
}

copyConfiguration(_configurationId: string, _newName: string) {
return Promise.resolve("");
}
Expand Down
1 change: 1 addition & 0 deletions designer/server/plugins/designer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const designerPlugin = {
});

server.route(newConfig.registerNewFormWithRunner);
server.route(api.loginRedirect);
server.route(api.getFormWithId);
server.route(api.putFormWithId);
server.route(api.getAllPersistedConfigurations);
Expand Down
54 changes: 42 additions & 12 deletions designer/server/plugins/routes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,31 @@ const getPublished = async function (id) {
return payload.toString();
};

export const loginRedirect: ServerRoute = {
method: "GET",
path: "/api/login",
options: {
handler: (_, h) => {
return h.redirect(config.managementUrl);
},
},
};

export const getFormWithId: ServerRoute = {
// GET DATA
method: "GET",
path: "/api/{id}/data",
options: {
handler: async (request, h) => {
const { id } = request.params;
const { persistenceService } = request.services([]);
let formJson = newFormJson;
try {
const response = await getPublished(id);
const { values } = JSON.parse(response);

if (values) {
formJson = values;
}
const response = await persistenceService.getConfigurationForUser(
`${id}`,
request.state["user"]
);
formJson = JSON.parse(response);
} catch (error) {
request.logger.error(error);
}
Expand Down Expand Up @@ -61,11 +71,22 @@ export const putFormWithId: ServerRoute = {

throw new Error("Schema validation failed, reason: " + error.message);
}
await persistenceService.uploadConfiguration(
`${id}`,
JSON.stringify(value)
);
await publish(id, value);

if (request.state["user"]) {
await persistenceService.uploadConfigurationForUser(
`${id}`,
JSON.stringify(value),
request.state["user"]
);
} else {
await persistenceService.uploadConfiguration(
`${id}`,
JSON.stringify(value)
);
}

// Remove publishing for now
// await publish(id, value);
return h.response({ ok: true }).code(204);
} catch (err) {
request.logger.error("Designer Server PUT /api/{id}/data error:", err);
Expand All @@ -89,7 +110,16 @@ export const getAllPersistedConfigurations: ServerRoute = {
handler: async (request, h): Promise<ResponseObject | undefined> => {
const { persistenceService } = request.services([]);
try {
const response = await persistenceService.listAllConfigurations();
let response;

if (request.state["user"]) {
response = await persistenceService.listAllConfigurationsForUser(
request.state["user"]
);
} else {
response = await persistenceService.listAllConfigurations();
}

return h.response(response).type("application/json");
} catch (error) {
request.server.log(["error", "/configurations"], error);
Expand Down
Loading