-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit hooks up the Web UI to use our authentication APIs. It can now query for a list of acceptable authentication methods and show the user a web form for filling out the required credentials. The UI will monitor requests and if it detects an Unauthorized status will immediately open the auth flow. So as with the CLI, auth happens automagically when required and not otherwise.
- Loading branch information
Showing
17 changed files
with
2,348 additions
and
1,812 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
nodeLinker: node-modules | ||
|
||
yarnPath: .yarn/releases/yarn-4.1.1.cjs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { JSONSchema7 } from "json-schema"; | ||
import { ListRequest, ListResponse } from "./baseInterfaces"; | ||
|
||
// A request to list the authentication methods that the node supports. | ||
export interface ListAuthnMethodsRequest extends ListRequest { } | ||
|
||
// A response listing the authentication methods that the node supports. | ||
export interface ListAuthnMethodsResponse extends ListResponse { | ||
// The name of a method mapped to that method's requirements. | ||
// The name must be subsequently used to submit data. | ||
Methods: {[key: string]: Requirement} | ||
} | ||
|
||
// A requirement of an authn method, with the params varying per-type. | ||
export type Requirement = { | ||
type: "challenge" | ||
params: ChallengeRequirement | ||
} | { | ||
type: "ask" | ||
params: AskRequirement | ||
} | ||
|
||
// The "ask" type just gives the client a JSON Schema that describes the fields | ||
// it needs to collect from the user and submit. | ||
export type AskRequirement = JSONSchema7 | ||
|
||
// The "challenge" type gives the client an input phrase it must sign using its | ||
// private key. | ||
export interface ChallengeRequirement { | ||
InputPhrase: string | ||
} | ||
|
||
// A request to authenticate using a given method, including any credentials. | ||
export interface AuthnRequest { | ||
Name: string | ||
MethodData: AskRequest | ChallengeRequest | ||
} | ||
|
||
// The "ask" type needs the fields requested from the user by the JSON Schema. | ||
export type AskRequest = {[key: string]: string} | ||
|
||
// The "challenge" type needs the signature of the phrase and the associated | ||
// public key. | ||
export interface ChallengeRequest { | ||
PhraseSignature: string | ||
PublicKey: string | ||
} | ||
|
||
export interface AuthnResponse { | ||
Authentication: Authentication | ||
} | ||
|
||
// A response from trying to authenticate. | ||
export interface Authentication { | ||
// Whether the authentication was successful. | ||
success: boolean | ||
// Any additional info about why authentication was successful or not. | ||
reason?: string | ||
// The token the client should use in subsequent API requests. | ||
token?: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
form { | ||
display: grid; | ||
grid-template-columns: 4fr 6fr; | ||
row-gap: 1em; | ||
column-gap: 1em; | ||
|
||
h3 { | ||
margin: 0; | ||
grid-column: span 2; | ||
} | ||
|
||
label { | ||
text-transform: capitalize; | ||
} | ||
|
||
label[data-required=true]::after { | ||
content: "*"; | ||
color: red; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from "react"; | ||
import { JSONSchema7, JSONSchema7Definition } from "json-schema"; | ||
import { AskRequest, AskRequirement, AuthnRequest } from "../../helpers/authInterfaces"; | ||
import "./AskInput.module.scss" | ||
|
||
interface AskInputProps { | ||
name: string | ||
requirement: AskRequirement | ||
authenticate: (req: AuthnRequest) => void | ||
cancel: () => void | ||
} | ||
|
||
function propertyToInputType(property: JSONSchema7Definition): React.HTMLInputTypeAttribute { | ||
if (typeof property === "boolean") { | ||
return "text" | ||
} | ||
|
||
if (property.writeOnly) { | ||
return "password" | ||
} | ||
|
||
switch (property.type) { | ||
case "number": | ||
case "integer": | ||
return "number" | ||
case "boolean": | ||
return "checkbox" | ||
default: | ||
return "text" | ||
} | ||
} | ||
|
||
export const AskInput: React.FC<AskInputProps> = (props: AskInputProps) => { | ||
const requirement = props.requirement | ||
const properties = requirement.properties ?? {} | ||
const fields = Object.keys(properties) | ||
const required = requirement.required ?? [] | ||
|
||
// Sort by fields listed in required order | ||
fields.sort((a, b) => required.indexOf(a) - required.indexOf(b)) | ||
|
||
const inputs = fields.map(field => { | ||
const property = properties[field] as JSONSchema7 | ||
return <> | ||
<label htmlFor={field} data-required={required.includes(field)}> | ||
{field} | ||
</label> | ||
<input | ||
name={field} | ||
type={propertyToInputType(property)} | ||
required={required.includes(field)} /> | ||
</> | ||
}) | ||
|
||
const submit: React.FormEventHandler<HTMLFormElement> = (event) => { | ||
event.preventDefault() | ||
|
||
const formData = new FormData(event.currentTarget) | ||
const objectData: AskRequest = {} | ||
formData.forEach((value, key) => {objectData[key] = value.toString()}) | ||
|
||
const request: AuthnRequest = {Name: props.name, MethodData: objectData} | ||
props.authenticate(request) | ||
|
||
return false | ||
} | ||
|
||
return <form onSubmit={submit} onReset={props.cancel}> | ||
<h3>Authenticate using {props.name.replaceAll(/[^A-Za-z0-9]/g, ' ')}</h3> | ||
{...inputs} | ||
<input type="reset" value="Cancel"/> | ||
<input type="submit" value="Authenticate"/> | ||
</form> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
@import "../../styles/container"; | ||
@import "../../styles/button"; | ||
@import "../../styles/variables"; | ||
|
||
.flow { | ||
@include container; | ||
width: 50%; | ||
margin-left: auto; | ||
margin-right: auto; | ||
min-width: 250px; | ||
|
||
button, input[type=submit], input[type=reset] { | ||
@include button; | ||
} | ||
|
||
input[type=reset] { | ||
background-color: $grey-label; | ||
color: $grey-text; | ||
|
||
&:hover { | ||
background-color: rgba($grey-label, 0.7); | ||
} | ||
|
||
&:active { | ||
background-color: rgba($grey-label, 0.8); | ||
} | ||
} | ||
|
||
h3 { | ||
margin: 0; | ||
} | ||
|
||
ul { | ||
list-style: none; | ||
padding: 0; | ||
|
||
li { | ||
margin-bottom: 1em; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React, { useState } from "react" | ||
import { toast } from "react-toastify" | ||
import { Layout } from "../../layout/Layout" | ||
import { AuthnRequest } from "../../helpers/authInterfaces" | ||
import { bacalhauAPI } from "../../services/bacalhau" | ||
import { useLocation, useNavigate } from "react-router-dom" | ||
import { AxiosError } from "axios" | ||
import styles from "./Flow.module.scss"; | ||
import { MethodPicker } from "./MethodPicker" | ||
|
||
export const Flow: React.FC<{}> = ({ }) => { | ||
const location = useLocation() | ||
const navigate = useNavigate() | ||
const [inputForm, setInputForm] = useState<React.ReactElement>() | ||
|
||
const submitAuthnRequest = (req: AuthnRequest) => { | ||
bacalhauAPI.authenticate(req).then(auth => { | ||
if (!auth.success) { | ||
toast.error("Failed to authenticate you: " + auth.reason) | ||
return | ||
} | ||
|
||
if ("prev" in location.state && "pathname" in location.state.prev) { | ||
// If we were navigated here from an auth error on another page, | ||
// return to that page to continue what we were doing. | ||
navigate(location.state.prev.pathname) | ||
} else { | ||
toast.info("Authentication successful.") | ||
} | ||
}).catch(error => { | ||
let errorText = error | ||
if (error instanceof AxiosError) { | ||
errorText = error.response?.statusText | ||
} | ||
toast.error("Failed to authenticate you: " + errorText) | ||
}) | ||
} | ||
|
||
return <Layout pageTitle="Authenticate"> | ||
<div className={styles.flow}> | ||
{inputForm ?? <MethodPicker | ||
setInputForm={setInputForm} | ||
authenticate={submitAuthnRequest} /> | ||
} | ||
</div> | ||
</Layout> | ||
} |
Oops, something went wrong.