re-add authentication section

This commit is contained in:
Malte Teichert
2024-05-20 18:38:22 +02:00
parent d4fd0f2fbe
commit a0cc87c96c
6 changed files with 234 additions and 268 deletions

15
src/app.d.ts vendored
View File

@@ -7,3 +7,18 @@ declare namespace App {
// interface Error {} // interface Error {}
// interface Platform {} // interface Platform {}
} }
declare namespace Oauth2 {
interface Oauth2Flow {
authorizationUrl: string;
scopes: Record<string, string>;
refreshUrl?: string;
}
interface Oauth2FlowTemplates {
implicit: Oauth2Flow;
password: Oauth2Flow;
clientCredentials: Oauth2Flow;
authorizationCode: Oauth2Flow & { tokenUrl: string };
}
}

View File

@@ -1,71 +1,49 @@
import type { import type { OpenAPIV3_1 } from './openAPITypes';
ApiKeyAuth,
BasicAuth,
BearerAuth,
CookieAuth,
OAuth2Auth,
OpenIdConnectAuth
} from './types/auth';
export const basicAuthTemplate: BasicAuth = { export const basicAuthTemplate: OpenAPIV3_1.HttpSecurityScheme = {
identifier: '',
type: 'http', type: 'http',
scheme: 'basic' scheme: 'basic',
description: undefined
}; };
export const bearerAuthTemplate: BearerAuth = { export const bearerAuthTemplate: OpenAPIV3_1.HttpSecurityScheme = {
identifier: '',
type: 'http', type: 'http',
scheme: 'bearer', scheme: 'bearer',
bearerFormat: '' bearerFormat: undefined,
description: undefined
}; };
export const apiKeyAuthTemplate: ApiKeyAuth = { export const apiKeyAuthTemplate: OpenAPIV3_1.ApiKeySecurityScheme = {
identifier: '',
type: 'apiKey', type: 'apiKey',
in: 'header', in: 'header', // or 'query' or 'cookie'
name: '' name: '',
description: undefined
}; };
export const openIdAuthTemplate: OpenIdConnectAuth = { export const openIdAuthTemplate: OpenAPIV3_1.OpenIdSecurityScheme = {
identifier: '',
type: 'openIdConnect', type: 'openIdConnect',
openIdConnectUrl: '' openIdConnectUrl: '',
description: undefined
}; };
export const oauth2AuthTemplate: OAuth2Auth = { export const oauth2AuthTemplate: OpenAPIV3_1.OAuth2SecurityScheme = {
identifier: '',
type: 'oauth2', type: 'oauth2',
description: '', flows: {},
flows: [] description: undefined
}; };
export const cookieAuthTemplate: CookieAuth = { const baseOauth2Flow: Oauth2.Oauth2Flow = {
identifier: '', authorizationUrl: '',
type: 'apiKey', scopes: {},
in: 'cookie', refreshUrl: undefined
name: ''
}; };
export const oauth2FlowTemplates = { export const oauth2FlowTemplates: Oauth2.Oauth2FlowTemplates = {
implicit: baseOauth2Flow,
password: baseOauth2Flow,
clientCredentials: baseOauth2Flow,
authorizationCode: { authorizationCode: {
name: 'authorizationCode', ...baseOauth2Flow,
authorizationUrl: '',
tokenUrl: '',
scopes: []
},
implicit: {
name: 'implicit',
authorizationUrl: '',
scopes: []
},
password: {
name: 'password',
tokenUrl: '',
scopes: []
},
clientCredentials: {
name: 'clientCredentials',
tokenUrl: '' tokenUrl: ''
} }
}; };

View File

@@ -1,169 +1,129 @@
<script lang="ts"> <script lang="ts">
import { oauth2FlowTemplates } from '$lib/authTemplates'; import { oauth2FlowTemplates } from '$lib/authTemplates';
import type { SecuritySchema } from '$lib/types'; import type { OpenAPIV3_1 } from '$lib/openAPITypes';
import OAuthFlow from './OAuthFlow.svelte'; import OAuthFlow from '$lib/components/atoms/OAuthFlow.svelte';
export let data: SecuritySchema; export let schema: OpenAPIV3_1.SecuritySchemeObject;
let oauthFlow: string; let availableFlows: string[] = ['implicit', 'password', 'clientCredentials', 'authorizationCode'];
const addOAuthFlow = () => {
if (!data.hasOwnProperty('flows')) {
return;
}
const tempData = data;
let newFlow;
switch (oauthFlow) { // remove flows that are already in Object.keys(schema.flows)
case 'authorizationCode': // @ts-expect-error - security schema definition is lacking a bit
newFlow = oauth2FlowTemplates.authorizationCode; availableFlows = availableFlows.filter((flow) => !Object.keys(schema.flows).includes(flow));
break;
case 'implicit':
newFlow = oauth2FlowTemplates.implicit;
break;
case 'password':
newFlow = oauth2FlowTemplates.password;
break;
case 'clientCredentials':
newFlow = oauth2FlowTemplates.clientCredentials;
break;
default:
newFlow = undefined;
break;
}
if (newFlow) { let flowType: string;
// @ts-expect-error - this is only called when data.flows exists const addOauthFlow = () => {
tempData.flows.push(newFlow); if (!flowType) return;
data = tempData; // @ts-expect-error - security schema definition is lacking a bit
} schema.flows[flowType] = oauth2FlowTemplates[flowType];
// remove used flow from availableFlows
availableFlows = availableFlows.filter((flow) => flow !== flowType);
}; };
const removeFlow = (index: number) => { const removeOauthFlow = (flow: string) => {
if (!data.hasOwnProperty('flows')) { // @ts-expect-error - security schema definition is lacking a bit
return; let tempFlows = schema.flows;
} delete tempFlows[flow];
let tempData = data; // @ts-expect-error - security schema definition is lacking a bit
// @ts-expect-error - this is only called when data.flows exists schema.flows = tempFlows;
tempData.flows.splice(index, 1);
data = tempData; // add flow back to availableFlows
availableFlows = [...availableFlows, flow];
}; };
</script> </script>
<div class="flex flex-col gap-2"></div> <div class="space-y-2">
<div class="space-y-4"> {#if schema.type === 'http' && schema.scheme === 'basic'}
{#if data.type == 'http' && data.scheme == 'basic'} <h3 class="h3">Basic Authentication</h3>
<h3 class="h3">Basic Auth</h3>
<p>Basic authentication is a simple authentication scheme built into the HTTP protocol.</p>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<input class="input" type="text" placeholder="basicAuth" bind:value={data.identifier} />
</label>
{:else if data.type == 'http' && data.scheme == 'bearer'}
<h3 class="h3">Bearer Auth</h3>
<p>Basic authentication is a simple authentication scheme built into the HTTP protocol.</p>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<input class="input" type="text" placeholder="bearerAuth" bind:value={data.identifier} />
</label>
<label class="space-y-2">
<h4 class="h4">Format: arbitrary string for documentation purposes</h4>
<input class="input" type="text" placeholder="JWT" bind:value={data.identifier} />
</label>
{:else if data.type == 'apiKey' && data.in == 'header'}
<!-- Cookie API Keys handled by CookieAuth -->
<h3 class="h3">API Key</h3>
<p> <p>
API keys are a simple authentication method that the client provides when making API requests. Basic authentication is a simple authentication scheme built into the HTTP protocol. No
configuration required.
</p> </p>
<label class="space-y-2"> {:else if schema.type === 'http' && schema.scheme === 'bearer'}
<h4 class="h4">Unique name for the security scheme</h4> <h3 class="h3">Bearer Authentication</h3>
<input class="input" type="text" placeholder="apiKeyAuth" bind:value={data.identifier} />
</label>
<label class="space-y-2">
<h4 class="h4">Location</h4>
<select class="input">
<option value="header" selected>Header</option>
<option value="query">Query</option>
</select>
</label>
<label class="space-y-2">
<h4 class="h4">Name (required)</h4>
<input class="input" type="text" placeholder="X-API-Key" />
</label>
{:else if data.type == 'openIdConnect'}
<h3 class="h3">OpenID</h3>
<p> <p>
OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol and Bearer authentication (also called token authentication) is an HTTP authentication scheme that
supported by some OAuth 2.0 providers, such as Google and Azure Active Directory. involves security tokens called bearer tokens.
</p> </p>
<label class="space-y-2"> <label>
<h4 class="h4">Unique name for the security scheme</h4> <h5 class="h5">Description</h5>
<input class="input" type="text" placeholder="openIdAuth" bind:value={data.identifier} /> <p class="text-sm">Human-readable information. May contain Markdown.</p>
<textarea class="textarea" placeholder="Description" />
</label> </label>
<label> <label>
<h4 class="h4">OpenID Connect URL</h4> <h5 class="h5">Bearer format</h5>
<p class="text-sm">A hint to the client to identify how the bearer token is formatted.</p>
<input type="text" class="input" placeholder="JWT" />
</label>
{:else if schema.type === 'apiKey'}
<h3 class="h3">API Key Authentication</h3>
<label>
<h5 class="h5">Location</h5>
<select class="input">
<option value="header">header</option>
<option value="query">query</option>
<option value="cookie">cookie</option>
</select>
</label>
<label>
<h5 class="h5">Name</h5>
<p class="text-sm">The name of the key parameter in the location.</p>
<input type="text" class="input" placeholder="api_key" />
</label>
<label>
<h5 class="h5">Description</h5>
<p class="text-sm">Human-readable information. May contain Markdown.</p>
<textarea class="textarea" placeholder="Description" />
</label>
{:else if schema.type === 'openIdConnect'}
<h3 class="h3">OpenID Connect Authentication</h3>
<label>
<h5 class="h5">OpenID Connect URL</h5>
<p class="text-sm">The URL must point to a JSON OpenID Connect Discovery document.</p>
<input <input
class="input"
type="url" type="url"
class="input"
placeholder="https://example.com/.well-known/openid-configuration" placeholder="https://example.com/.well-known/openid-configuration"
/> />
</label> </label>
{:else if data.type == 'oauth2'} <label>
<h3 class="h3">OAuth2</h3> <h5 class="h5">Description</h5>
<p> <p class="text-sm">Human-readable information. May contain Markdown.</p>
OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on <textarea class="textarea" placeholder="Description" />
a web server.
</p>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<input class="input" type="text" placeholder="oAuth" bind:value={data.identifier} />
</label> </label>
<label class="space-y-2"> {:else if schema.type === 'oauth2'}
<h4 class="h4">Description</h4> <h3 class="h3">Oauth2 Authentication</h3>
<textarea <label>
class="input rounded-container-token" <h5 class="h5">Description</h5>
placeholder="OAuth2 Authorization Description" <p class="text-sm">Human-readable information. May contain Markdown.</p>
/> <textarea class="textarea" placeholder="Description" />
</label> </label>
{#each data.flows as flow, index}
<OAuthFlow bind:flow /> <h5 class="h5">Flows</h5>
<span class="flex justify-center"> {#each Object.keys(schema.flows) as flow}
<div class="flex w-full justify-between items-center">
<h6 class="h6">{flow.charAt(0).toLocaleUpperCase() + flow.slice(1)}</h6>
<button <button
type="button" type="button"
class="btn variant-ringed-error hover:variant-filled-error" class="btn btn-sm variant-ringed-error hover:variant-filled-error"
on:click={() => { on:click={() => removeOauthFlow(flow)}
removeFlow(index);
}}
> >
Remove Flow Remove {flow}
</button> </button>
</span> </div>
<hr /> <OAuthFlow type={flow} flow={schema.flows[flow]} />
{/each} {/each}
<span class="flex justify-center items-center gap-2 max-w-md mx-auto">
<select class="input" bind:value={oauthFlow}> <span class="w-full flex justify-center gap-2">
<option value="authorizationCode">Authorization Code</option> <select class="input w-min" bind:value={flowType}>
<option value="implicit">Implicit</option> {#each availableFlows as flow}
<option value="password">Password</option> <option value={flow}>{flow}</option>
<option value="clientCredentials">Client Credentials</option> {/each}
</select> </select>
<button type="button" class="btn variant-filled-primary" on:click={addOAuthFlow}> <button type="button" class="btn btn-sm variant-filled-primary" on:click={addOauthFlow}>
Add OAuth2 Flow Add Flow
</button> </button>
</span> </span>
{:else if data.type == 'apiKey' && data.in == 'cookie'}
<h3 class="h3">Cookie Auth</h3>
<p>
Cookie authentication uses HTTP cookies to authenticate client requests and maintain session
information.
</p>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<input class="input" type="text" placeholder="cookieAuth" bind:value={data.identifier} />
</label>
<label class="space-y-2">
<h4 class="h4">Cookie Name (required)</h4>
<input class="input" type="text" placeholder="JSESSIONID" />
</label>
{/if} {/if}
</div> </div>

View File

@@ -1,23 +1,20 @@
<script lang="ts"> <script lang="ts">
import ScopeList from './ScopeList.svelte'; export let type: 'implicit' | 'authorizationCode' | 'password' | 'clientCredentials';
export let flow: Oauth2.Oauth2Flow | (Oauth2.Oauth2Flow & { tokenUrl: string });
import type { Flows } from '$lib/types/auth';
export let flow: Flows;
const addScope = () => { const addScope = () => {
flow.scopes = [...flow.scopes, [{ scope: '', description: '' }]]; const name = prompt('Enter scope');
}; if (!name) return;
const removeScope = (index: number) => { flow.scopes[name] = '';
flow.scopes = flow.scopes.filter((_, i) => i !== index);
}; };
const removeScope = (index: number) => {};
</script> </script>
<div class="flex flex-col gap-4"> <div class="border-token rounded-container-token p-4">
{#if flow.name == 'authorizationCode'} <div class="ml-4 flex flex-col gap-4">
<h3 class="h3">Authorization Code</h3> <label>
<label class="space-y-2"> <h5 class="h5">Authorization URL</h5>
<h4 class="h4">Authorization URL</h4> <p class="text-sm">The authorization URL to be used for this flow.</p>
<input <input
type="text" type="text"
name="authorizationURL" name="authorizationURL"
@@ -26,55 +23,72 @@
bind:value={flow.authorizationUrl} bind:value={flow.authorizationUrl}
/> />
</label> </label>
<label class="space-y-2"> {#if type === 'authorizationCode'}
<h4 class="h4">Token URL</h4> <label>
<h5 class="h5">Token URL</h5>
<p class="text-sm">The token URL to be used for this flow.</p>
<input
type="text"
name="tokenURL"
class="input"
placeholder="https://api.example.com/oauth2/token"
bind:value={flow.tokenUrl}
/>
</label>
{/if}
<label>
<h5 class="h5">Refresh URL</h5>
<p class="text-sm">The refresh URL to be used for this flow. (optional)</p>
<input <input
type="text" type="text"
name="tokenURL" name="refreshURL"
class="input" class="input"
placeholder="https://api.example.com/oauth2/token" placeholder="https://api.example.com/oauth2/refresh"
bind:value={flow.tokenUrl} bind:value={flow.refreshUrl}
/> />
</label> </label>
<ScopeList bind:flow /> <label>
{:else if flow.name == 'implicit'} <h5 class="h5">Scopes</h5>
<h3 class="h3">Implicit</h3> <p class="text-sm">The available scopes for this flow.</p>
<label class="space-y-2"> <table class="table">
<h4 class="h4">Authorization URL</h4> <tbody>
<input {#each Object.keys(flow.scopes) as scope, index}
type="text" <tr>
name="authorizationURL" <td class="!text-lg">
class="input" {scope}
placeholder="https://api.example.com/oauth2/authorize" </td>
bind:value={flow.authorizationUrl} <td class="w-full">
/> <input
type="text"
name="scope"
class="input"
placeholder="description of the scope"
bind:value={flow.scopes[scope]}
/>
</td>
<td>
<button
type="button"
class="btn variant-ringed-error hover:variant-filled-error"
on:click={() => removeScope(index)}
>
Remove
</button>
</td>
</tr>
{/each}
</tbody>
</table>
<span class="w-full flex justify-center">
<button
type="button"
class="btn btn-sm variant-filled-primary"
class:mt-2={Object.keys(flow.scopes).length !== 0}
on:click={addScope}
>
Add Scope
</button>
</span>
</label> </label>
<ScopeList bind:flow /> </div>
{:else if flow.name == 'password'}
<h3 class="h3">Password</h3>
<label class="space-y-2">
<h4 class="h4">Token URL</h4>
<input
type="text"
name="tokenURL"
class="input"
placeholder="https://api.example.com/oauth2/token"
bind:value={flow.tokenUrl}
/>
</label>
<ScopeList bind:flow />
{:else if flow.name == 'clientCredentials'}
<h3 class="h3">Client Credentials</h3>
<label class="space-y-2">
<h4 class="h4">Token URL</h4>
<input
type="text"
name="tokenURL"
class="input"
placeholder="https://api.example.com/oauth2/token"
bind:value={flow.tokenUrl}
/>
</label>
<ScopeList bind:flow />
{/if}
</div> </div>

View File

@@ -1,10 +1,9 @@
<!-- <script lang="ts"> <script lang="ts">
import { openApiStore } from '$lib'; import { openApiStore } from '$lib';
import { import {
apiKeyAuthTemplate, apiKeyAuthTemplate,
basicAuthTemplate, basicAuthTemplate,
bearerAuthTemplate, bearerAuthTemplate,
cookieAuthTemplate,
oauth2AuthTemplate, oauth2AuthTemplate,
openIdAuthTemplate openIdAuthTemplate
} from '$lib/authTemplates'; } from '$lib/authTemplates';
@@ -12,7 +11,7 @@
let selectedSchema: string; let selectedSchema: string;
const addSecuritySchema = () => { const addSecuritySchema = () => {
let tempSchemaList = $openApiStore.securitySchemas; let tempSchemaList = $openApiStore.security;
let newSchema; let newSchema;
switch (selectedSchema) { switch (selectedSchema) {
case 'basicAuth': case 'basicAuth':
@@ -30,9 +29,6 @@
case 'oAuthSample': case 'oAuthSample':
newSchema = oauth2AuthTemplate; newSchema = oauth2AuthTemplate;
break; break;
case 'cookieAuth':
newSchema = cookieAuthTemplate;
break;
default: default:
newSchema = undefined; newSchema = undefined;
break; break;
@@ -40,30 +36,34 @@
if (newSchema) { if (newSchema) {
tempSchemaList = [...tempSchemaList, newSchema]; tempSchemaList = [...tempSchemaList, newSchema];
$openApiStore.securitySchemas = tempSchemaList; $openApiStore.security = tempSchemaList;
} }
}; };
const removeSecuritySchema = (index: number) => { const removeSecuritySchema = (index: number) => {
let tempSchemaList = $openApiStore.securitySchemas; let tempSchemaList = $openApiStore.security;
tempSchemaList.splice(index, 1); tempSchemaList.splice(index, 1);
$openApiStore.securitySchemas = tempSchemaList; $openApiStore.security = tempSchemaList;
}; };
</script> </script>
<form class="container mx-auto card px-6 py-4 space-y-4"> <form
{#each $openApiStore.securitySchemas as schema, index} class="container mx-auto border-token rounded-container-token bg-surface-backdrop-token px-6 py-4 space-y-4"
<AuthenticationItem bind:data={schema} /> >
<span class="flex justify-center"> {#each $openApiStore.security as schema, index}
<button <div class="card w-full p-4">
type="button" <div class="flex flex-row-reverse w-full">
class="btn variant-ringed-error hover:variant-filled-error" <button
on:click={() => { type="button"
removeSecuritySchema(index); class="btn btn-sm variant-ringed-error hover:variant-filled-error"
}} on:click={() => {
> removeSecuritySchema(index);
Remove Security Schema }}
</button> >
</span> Remove schema
</button>
</div>
<AuthenticationItem bind:schema />
</div>
<hr /> <hr />
{/each} {/each}
@@ -74,10 +74,9 @@
<option value="ApiKeyAuth">API Key Auth</option> <option value="ApiKeyAuth">API Key Auth</option>
<option value="openId">OpenID</option> <option value="openId">OpenID</option>
<option value="oAuthSample">OAuth2</option> <option value="oAuthSample">OAuth2</option>
<option value="cookieAuth">Cookie Auth</option>
</select> </select>
<button type="button" class="btn variant-filled-primary" on:click={addSecuritySchema}> <button type="button" class="btn variant-filled-primary" on:click={addSecuritySchema}>
Add Security Schema Add Security Schema
</button> </button>
</span> </span>
</form> --> </form>

View File

@@ -29,7 +29,7 @@
{#if $tabSet === 0} {#if $tabSet === 0}
<Info /> <Info />
{:else if $tabSet === 1} {:else if $tabSet === 1}
<!-- <Authentication /> --> <Authentication />
{:else if $tabSet === 2} {:else if $tabSet === 2}
<Servers /> <Servers />
{:else if $tabSet === 3} {:else if $tabSet === 3}