fix bugs by prev commit
Some checks failed
Deploy site to Pages / build (push) Failing after 7m49s

This commit is contained in:
leshe4ka46 2024-06-21 16:40:48 +03:00
parent a4a79e45b7
commit a0de7d4da6
Signed by: leshe4ka
SSH Key Fingerprint: SHA256:8KQ7Kw26acmm2HT2UJmE1J0rKhEJTtx33MVbGLovO1I
14 changed files with 7623 additions and 4216 deletions

11606
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -12,7 +12,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.25.0",
"axios": "^0.28.0",
"dayjs": "^1.11.9",
"mui-chips-input": "^2.1.3",
"nth-check": "^2.0.1",

@ -23,6 +23,25 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Вход</title>
<style>
:root {
--primary-color: #004fa1;
--background-color: #ffffff;
--text-color: #000000;
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: #000000;
--text-color: #ffffff;
}
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
</style>
</head>
<body data-externalurl="">

@ -1,6 +1,6 @@
import { Grid } from "@mui/material";
import React, { useState } from "react";
import Webauthn from "./Webauthn.tsx";
import Webauthn from "./Webauthn";
interface Props {
LoggedIn: boolean;

@ -3,14 +3,15 @@ import {
performAssertionCeremony,
performAttestationCeremony,
performAssertionCeremonyConditional,
} from "../services/WebauthnService.ts";
import { AssertionResult, AttestationResult } from "../models/Webauthn.ts";
} from "../services/WebauthnService";
import { AssertionResult, AttestationResult } from "../models/Webauthn";
import { Button, Grid, Box } from "@mui/material";
import KeyIcon from "@mui/icons-material/Key";
import { getInfo } from "../services/APIService.ts";
import { getInfo } from "../services/APIService";
//import axios from "axios";
//import { hexMD5 } from "../utils/MD5.js";
import { radiusLogin } from "../services/ClientService.ts";
import { radiusLogin } from "../services/ClientService";
interface Props {
setDebugMessage: React.Dispatch<React.SetStateAction<string>>;
Discoverable: boolean;
@ -21,17 +22,39 @@ interface Props {
}
const Webauthn = function (props: Props) {
const [abortController, setabortController] =
React.useState<AbortController>();
const [abortSignal, setabortSignal] = React.useState<AbortSignal>();
const [req, setReq] = React.useState<PublicKeyCredentialRequestOptions>();
React.useEffect(() => {
console.log("got first time");
performAssertionCeremonyConditional(true, props.mac).then(res => {
if (res == AssertionResult.Success) {
handleDiscoverableLoginSuccess();
}
});
abortController?.abort();
if (!props.loggedIn) {
var abortControllerlocal = new AbortController();
setabortController(abortControllerlocal);
setabortSignal(abortControllerlocal.signal);
console.log(abortControllerlocal);
console.log("got first time");
}
// eslint-disable-next-line
}, [props.loggedIn]);
React.useEffect(() => {
if (!props.loggedIn && abortController) {
console.log("running conditional attestation");
performAssertionCeremonyConditional(
true,
props.mac,
abortController,
setReq
).then(res => {
if (res === AssertionResult.Success) {
handleDiscoverableLoginSuccess();
}
});
}
// eslint-disable-next-line
}, [abortController,abortSignal,props.loggedIn, props.mac]);
const handleDiscoverableLoginSuccess = async () => {
const info = await getInfo();
if (info != null) {
props.setBothUsername(info.username);
}
@ -39,7 +62,7 @@ const Webauthn = function (props: Props) {
const handleAttestationClick = async (discoverable: boolean = false) => {
props.setDebugMessage("Attempting Webauthn Attestation");
abortController?.abort();
const result = await performAttestationCeremony(discoverable);
switch (result) {
@ -79,43 +102,6 @@ const Webauthn = function (props: Props) {
}
};
const handleResult = (result:AttestationResult) => {
switch (result) {
case AttestationResult.Success:
props.setDebugMessage("Successful attestation.");
handleDiscoverableLoginSuccess();
break;
case AttestationResult.FailureSupport:
props.setDebugMessage(
"Your browser does not appear to support the configuration."
);
break;
case AttestationResult.FailureSyntax:
props.setDebugMessage(
"The attestation challenge was rejected as malformed or incompatible by your browser."
);
break;
case AttestationResult.FailureWebauthnNotSupported:
props.setDebugMessage(
"Your browser does not support the WebAuthN protocol."
);
break;
case AttestationResult.FailureUserConsent:
props.setDebugMessage("You cancelled the attestation request.");
break;
case AttestationResult.FailureUserVerificationOrResidentKey:
props.setDebugMessage(
"Your device does not support user verification or resident keys but this was required."
);
break;
case AttestationResult.FailureExcluded:
props.setDebugMessage("You have registered this device already.");
break;
case AttestationResult.FailureUnknown:
props.setDebugMessage("An unknown error occurred.");
break;
}
}
const radiusAuth = () => {
console.log("auth");
radiusLogin().then(ret => {
@ -154,9 +140,14 @@ const Webauthn = function (props: Props) {
const handleAssertionClick = async () => {
props.setDebugMessage("Attempting Webauthn Assertion");
//alert(searchParams.get('to'));
console.log(abortController);
abortController?.abort();
console.log(abortController);
const result = await performAssertionCeremony(
props.Discoverable,
props.mac
props.mac,
req,
setReq
);
switch (result) {

@ -1,4 +1,4 @@
import { getEmbeddedVariable } from "../utils/Configuration.ts";
import { getEmbeddedVariable } from "../utils/Configuration";
const ExternalURL = getEmbeddedVariable("externalurl")

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import Login from "./pages/LoginPage.tsx";
import Login from "./pages/LoginPage";
import "@fontsource/roboto/300.css"; // Import the font for font-weight: 300
import "@fontsource/roboto/400.css"; // Import the font for font-weight: 400
import "@fontsource/roboto/500.css"; // Import the font for font-weight: 500

@ -46,10 +46,10 @@ export interface AuthenticatorAssertionResponseJSON
}
export interface AuthenticatorAttestationResponseFuture extends AuthenticatorAttestationResponse {
getTransports?: () => AuthenticatorTransport[];
getAuthenticatorData?: () => ArrayBuffer;
getPublicKey?: () => ArrayBuffer;
getPublicKeyAlgorithm?: () => COSEAlgorithmIdentifier[];
getTransports(): AuthenticatorTransport[];
getAuthenticatorData(): ArrayBuffer;
getPublicKey(): ArrayBuffer | null;
getPublicKeyAlgorithm(): COSEAlgorithmIdentifier;
}
export interface AttestationPublicKeyCredential extends PublicKeyCredential {
@ -57,7 +57,7 @@ export interface AttestationPublicKeyCredential extends PublicKeyCredential {
}
export interface AuthenticatorAttestationResponseJSON
extends Omit<AuthenticatorAttestationResponseFuture, "clientDataJSON" | "attestationObject"> {
extends Omit<AuthenticatorAttestationResponseFuture, "clientDataJSON" | "attestationObject" | "getTransports" | "getAuthenticatorData" | "getPublicKey" | "getPublicKey" | "getPublicKeyAlgorithm"> {
clientDataJSON: string;
attestationObject: string;
}

@ -25,13 +25,13 @@ import Brightness7Icon from "@mui/icons-material/Brightness7";
import LogoutIcon from "@mui/icons-material/Logout";
import u2fApi from "u2f-api";
import { logout, login, radiusLogin } from "../services/ClientService.ts";
import { logout, login, radiusLogin } from "../services/ClientService";
import {
isWebauthnSecure,
isWebauthnSupported,
} from "../services/WebauthnService.ts";
import { getInfo } from "../services/APIService.ts";
import SecurityKey from "../components/SecurityKey.tsx";
} from "../services/WebauthnService";
import { getInfo } from "../services/APIService";
import SecurityKey from "../components/SecurityKey";
const totalTime = 5;
export default function Login() {
const searchParams = React.useMemo(
@ -140,7 +140,7 @@ export default function Login() {
elem.href="intent://networkcheck.kde.org#Intent;scheme=http;end"
elem.click();
}
function findNextTabStop(el) {
function findNextTabStop(el: EventTarget) {
var universe = document.querySelectorAll('input, button, select, textarea, a[href]');
var list = Array.prototype.filter.call(universe, function(item) {return item.tabIndex >= "0"});
var index = list.indexOf(el);

@ -1,7 +1,7 @@
import axios, {AxiosResponse} from "axios";
import {ErrorResponse, ServiceResponse} from "../models/API.ts";
import {Info} from "../models/Info.ts";
import {InfoPath} from "../constants/API.ts";
import {ErrorResponse, ServiceResponse} from "../models/API";
import {Info} from "../models/Info";
import {InfoPath} from "../constants/API";
function toErrorResponse<T>(resp: AxiosResponse<ServiceResponse<T>>): ErrorResponse | undefined {
if (resp.data && "status" in resp.data && resp.data["status"] === "KO") {
@ -10,8 +10,8 @@ function toErrorResponse<T>(resp: AxiosResponse<ServiceResponse<T>>): ErrorRespo
return undefined;
}
export function toData<T>(resp: AxiosResponse<ServiceResponse<T>>): T | undefined {
if (resp.data && "status" in resp.data && resp.data["status"] === "OK") {
export function toData<T>(resp: void | AxiosResponse<ServiceResponse<T>>): T | undefined {
if (resp?.data && "status" in resp.data && resp.data["status"] === "OK") {
return resp.data.data as T;
}
return undefined;
@ -27,7 +27,7 @@ export function hasServiceError<T>(resp: AxiosResponse<ServiceResponse<T>>) {
export async function getInfo() : Promise<Info | undefined> {
try {
const response = await axios.get<ServiceResponse<Info>>(InfoPath);
const response = await axios.get<ServiceResponse<Info>>(InfoPath).catch((err)=>{console.log(err)}).then((res)=>(res));
return toData<Info>(response);
} catch (e) {
console.log(e)

@ -1,6 +1,6 @@
import axios from "axios";
import { LoginBody } from "../models/API.ts";
import { LoginPath, LogoutPath, ManualLogin } from "../constants/API.ts";
import { LoginBody } from "../models/API";
import { LoginPath, LogoutPath, ManualLogin } from "../constants/API";
export async function login(username: string, password: string, mac: string): Promise<boolean> {
const body: LoginBody = {

@ -1,4 +1,4 @@
import { getBase64WebEncodingFromBytes, getBytesFromBase64 } from '../utils/Base64.ts';
import { getBase64WebEncodingFromBytes, getBytesFromBase64 } from '../utils/Base64';
import {
AssertionPublicKeyCredentialResult,
AssertionResult,
@ -13,10 +13,10 @@ import {
PublicKeyCredentialJSON,
PublicKeyCredentialRequestOptionsJSON,
PublicKeyCredentialRequestOptionsStatus
} from '../models/Webauthn.ts';
} from '../models/Webauthn';
import axios, { AxiosResponse } from "axios";
import { OptionalDataServiceResponse, ServiceResponse, SignInResponse } from "../models/API.ts";
import { AssertionPath, AttestationPath, DiscoverableAssertionPath, DiscoverableAttestationPath } from "../constants/API.ts";
import { OptionalDataServiceResponse, ServiceResponse, SignInResponse } from "../models/API";
import { AssertionPath, AttestationPath, DiscoverableAssertionPath, DiscoverableAttestationPath } from "../constants/API";
export function isWebauthnSecure(): boolean {
if (window.isSecureContext) {
@ -105,8 +105,10 @@ function encodeAttestationPublicKeyCredential(credential: AttestationPublicKeyCr
response: {
attestationObject: arrayBufferEncode(response.attestationObject),
clientDataJSON: arrayBufferEncode(response.clientDataJSON),
},
transports: transports,
authenticatorAttachment: credential.authenticatorAttachment
};
}
@ -285,18 +287,24 @@ async function getAssertionPublicKeyCredentialResult(requestOptions: PublicKeyCr
return result;
}
async function getAssertionPublicKeyCredentialResultConditional(requestOptions: PublicKeyCredentialRequestOptions): Promise<AssertionPublicKeyCredentialResult> {
async function getAssertionPublicKeyCredentialResultConditional(requestOptions: PublicKeyCredentialRequestOptions,abortSignal:AbortController): Promise<AssertionPublicKeyCredentialResult> {
const result: AssertionPublicKeyCredentialResult = {
result: AssertionResult.Success,
};
console.log(abortSignal)
try {
result.credential = (await navigator.credentials.get({mediation: "conditional",publicKey: requestOptions})) as PublicKeyCredential;
result.credential = (await navigator.credentials.get({signal: abortSignal.signal,
mediation: "conditional" as CredentialMediationRequirement,
publicKey: requestOptions})) as PublicKeyCredential;
} catch(e) {
result.result = AssertionResult.Failure;
const exception = e as DOMException;
if (exception !== undefined) {
if (exception.name === "AbortError") {
console.log("request aborted");
return result;
}
else if (exception !== undefined) {
result.result = getAssertionResultFromDOMException(exception, requestOptions);
return result;
@ -354,8 +362,9 @@ export async function performAttestationCeremony(discoverable: boolean = false):
return AttestationResult.Failure;
}
export async function performAssertionCeremony(discoverable: boolean = false, mac:string): Promise<AssertionResult> {
const assertionRequestOpts = await getAssertionRequestOptions(discoverable);
export async function performAssertionCeremony(discoverable: boolean = false, mac:string, req:PublicKeyCredentialRequestOptions|undefined,setReq:React.Dispatch<React.SetStateAction<PublicKeyCredentialRequestOptions | undefined>>): Promise<AssertionResult> {
const assertionRequestOpts = req === undefined ? await getAssertionRequestOptions(discoverable) : {options: req, status: 200};
if (assertionRequestOpts.status !== 200 || assertionRequestOpts.options == null) {
return AssertionResult.Failure;
@ -370,7 +379,7 @@ export async function performAssertionCeremony(discoverable: boolean = false, ma
}
const response = await postAssertionPublicKeyCredentialResult(assertionResult.credential, discoverable, mac);
setReq(undefined)
if (response.data.status === "OK" && response.status === 200) {
return AssertionResult.Success;
}
@ -378,14 +387,13 @@ export async function performAssertionCeremony(discoverable: boolean = false, ma
return AssertionResult.Failure;
}
export async function performAssertionCeremonyConditional(discoverable: boolean = false, mac:string): Promise<AssertionResult> {
export async function performAssertionCeremonyConditional(discoverable: boolean = false, mac:string,abortSignal:AbortController, setReq:React.Dispatch<React.SetStateAction<PublicKeyCredentialRequestOptions | undefined>>): Promise<AssertionResult> {
const assertionRequestOpts = await getAssertionRequestOptions(discoverable);
if (assertionRequestOpts.status !== 200 || assertionRequestOpts.options == null) {
return AssertionResult.Failure;
}
const assertionResult = await getAssertionPublicKeyCredentialResultConditional(assertionRequestOpts.options);
setReq(assertionRequestOpts.options)
const assertionResult = await getAssertionPublicKeyCredentialResultConditional(assertionRequestOpts.options,abortSignal);
if (assertionResult.result !== AssertionResult.Success) {
return assertionResult.result;
@ -395,7 +403,7 @@ export async function performAssertionCeremonyConditional(discoverable: boolean
console.log("navigator exited");
const response = await postAssertionPublicKeyCredentialResult(assertionResult.credential, discoverable, mac);
setReq(undefined)
if (response.data.status === "OK" && response.status === 200) {
return AssertionResult.Success;
}

@ -131,7 +131,7 @@ export function getBase64FromBytes(bytes : number[] | Uint8Array) : string {
return getBase64WebEncodingFromBytes(bytes);
}
export function getBytesFromBase64(value) {
export function getBytesFromBase64(value: string) {
value = value.replace(/-/g, "+").replace(/_/g, "/");
while (value.length % 4) {
value += "=";
@ -139,8 +139,15 @@ export function getBytesFromBase64(value) {
return Uint8Array.from(atob(value), c => c.charCodeAt(0));
}
export function getBase64WebEncodingFromBytes(value) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(value)))
export function getBase64WebEncodingFromBytes(value: number[] | Uint8Array) {
let byteArray: Uint8Array;
if (value instanceof Uint8Array) {
byteArray = value;
} else {
byteArray = new Uint8Array(value);
}
return btoa(String.fromCharCode.apply(null, Array.from(byteArray)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");

26
tsconfig.json Normal file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}