add authentication tab

This commit is contained in:
Malte Teichert
2024-05-20 00:09:46 +02:00
parent 13ecd58c9f
commit eadefcf8d4
12 changed files with 574 additions and 27 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vercel

71
src/lib/authTemplates.ts Normal file
View File

@@ -0,0 +1,71 @@
import type {
ApiKeyAuth,
BasicAuth,
BearerAuth,
CookieAuth,
OAuth2Auth,
OpenIdConnectAuth
} from './types/auth';
export const basicAuthTemplate: BasicAuth = {
identifier: '',
type: 'http',
scheme: 'basic'
};
export const bearerAuthTemplate: BearerAuth = {
identifier: '',
type: 'http',
scheme: 'bearer',
bearerFormat: ''
};
export const apiKeyAuthTemplate: ApiKeyAuth = {
identifier: '',
type: 'apiKey',
in: 'header',
name: ''
};
export const openIdAuthTemplate: OpenIdConnectAuth = {
identifier: '',
type: 'openIdConnect',
openIdConnectUrl: ''
};
export const oauth2AuthTemplate: OAuth2Auth = {
identifier: '',
type: 'oauth2',
description: '',
flows: []
};
export const cookieAuthTemplate: CookieAuth = {
identifier: '',
type: 'apiKey',
in: 'cookie',
name: ''
};
export const oauth2FlowTemplates = {
authorizationCode: {
name: 'authorizationCode',
authorizationUrl: '',
tokenUrl: '',
scopes: []
},
implicit: {
name: 'implicit',
authorizationUrl: '',
scopes: []
},
password: {
name: 'password',
tokenUrl: '',
scopes: []
},
clientCredentials: {
name: 'clientCredentials',
tokenUrl: ''
}
};

View File

@@ -0,0 +1,154 @@
<script lang="ts">
import { oauth2FlowTemplates } from '$lib/authTemplates';
import type { SecuritySchema } from '$lib/types';
import OAuthFlow from './OAuthFlow.svelte';
export let data: SecuritySchema;
let oauthFlow: string;
const addOAuthFlow = () => {
if (!data.hasOwnProperty('flows')) {
return;
}
const tempData = data;
let newFlow;
switch (oauthFlow) {
case 'authorizationCode':
newFlow = oauth2FlowTemplates.authorizationCode;
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) {
// @ts-expect-error - this is only called when data.flows exists
tempData.flows.push(newFlow);
data = tempData;
}
};
const removeFlow = (index: number) => {
if (!data.hasOwnProperty('flows')) {
return;
}
let tempData = data;
// @ts-expect-error - this is only called when data.flows exists
tempData.flows.splice(index, 1);
data = tempData;
};
</script>
<div class="flex flex-col gap-2"></div>
<div class="space-y-4">
{#if data.type == 'http' && data.scheme == 'basic'}
<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>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<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>
<label class="space-y-2">
<h4 class="h4">Unique name for the security scheme</h4>
<input class="input" type="text" placeholder="openIdAuth" bind:value={data.identifier} />
</label>
<label>
<h4 class="h4">OpenID Connect URL</h4>
<input
class="input"
type="url"
placeholder="https://example.com/.well-known/openid-configuration"
/>
</label>
{:else if data.type == 'oauth2'}
<h3 class="h3">OAuth2</h3>
<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 class="space-y-2">
<h4 class="h4">Description</h4>
<textarea
class="input rounded-container-token"
placeholder="OAuth2 Authorization Description"
/>
</label>
{#each data.flows as flow, index}
<OAuthFlow bind:flow />
<span class="flex justify-center">
<button
type="button"
class="btn variant-ringed-error hover:variant-filled-error"
on:click={() => {
removeFlow(index);
}}
>
Remove Flow
</button>
</span>
<hr />
{/each}
<span class="flex justify-center items-center gap-2 max-w-md mx-auto">
<select class="input" bind:value={oauthFlow}>
<option value="authorizationCode">Authorization Code</option>
<option value="implicit">Implicit</option>
<option value="password">Password</option>
<option value="clientCredentials">Client Credentials</option>
</select>
<button type="button" class="btn variant-filled-primary" on:click={addOAuthFlow}>
Add OAuth2 Flow
</button>
</span>
{:else if data.type == 'apiKey' && data.in == 'cookie'}
<h3 class="h3">Cookie Auth</h3>
<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}
</div>

View File

@@ -0,0 +1,80 @@
<script lang="ts">
import ScopeList from './ScopeList.svelte';
import type { Flows } from '$lib/types/auth';
export let flow: Flows;
const addScope = () => {
flow.scopes = [...flow.scopes, [{ scope: '', description: '' }]];
};
const removeScope = (index: number) => {
flow.scopes = flow.scopes.filter((_, i) => i !== index);
};
</script>
<div class="flex flex-col gap-4">
{#if flow.name == 'authorizationCode'}
<h3 class="h3">Authorization Code</h3>
<label class="space-y-2">
<h4 class="h4">Authorization URL</h4>
<input
type="text"
name="authorizationURL"
class="input"
placeholder="https://api.example.com/oauth2/authorize"
bind:value={flow.authorizationUrl}
/>
</label>
<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 == 'implicit'}
<h3 class="h3">Implicit</h3>
<label class="space-y-2">
<h4 class="h4">Authorization URL</h4>
<input
type="text"
name="authorizationURL"
class="input"
placeholder="https://api.example.com/oauth2/authorize"
bind:value={flow.authorizationUrl}
/>
</label>
<ScopeList bind:flow />
{: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>

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import type { Flows } from '$lib/types/auth';
export let flow: Flows;
const addScope = () => {
flow.scopes = [...flow.scopes, [{ scope: '', description: '' }]];
};
const removeScope = (index: number) => {
flow.scopes = flow.scopes.filter((_, i) => i !== index);
};
</script>
<h4 class="h4">Scopes</h4>
<table class="table">
<tbody>
{#each flow.scopes as scope, index}
{#each scope as item, i}
<tr>
<td class="px-4">
<input
type="text"
name="scope{index}-{i}"
class="input"
placeholder="scope"
bind:value={item.scope}
/>
</td>
<td class="px-2">
<input
type="text"
name="description{index}-{i}"
class="input"
placeholder="a short description of the scope (optional)"
bind:value={item.description}
/>
</td>
<td class="max-w-16 px-2">
<button
type="button"
class="btn variant-ringed-error hover:variant-filled-error"
on:click={() => {
removeScope(index);
}}
>
Remove Scope
</button>
</td>
</tr>
{/each}
{/each}
</tbody>
</table>
<span class="flex justify-center">
<button type="button" class="btn variant-filled-primary" on:click={addScope}>Add Scope</button
>
</span>

View File

@@ -19,7 +19,7 @@
<input
class="input"
name="url {id}"
placeholder="https://api.example.com"
placeholder="https://api.example.com/v1"
type="url"
bind:value={url}
/>

View File

@@ -0,0 +1,83 @@
<script lang="ts">
import { openApiStore } from '$lib';
import {
apiKeyAuthTemplate,
basicAuthTemplate,
bearerAuthTemplate,
cookieAuthTemplate,
oauth2AuthTemplate,
openIdAuthTemplate
} from '$lib/authTemplates';
import AuthenticationItem from '../atoms/AuthenticationItem.svelte';
let selectedSchema: string;
const addSecuritySchema = () => {
let tempSchemaList = $openApiStore.securitySchemas;
let newSchema;
switch (selectedSchema) {
case 'basicAuth':
newSchema = basicAuthTemplate;
break;
case 'bearerAuth':
newSchema = bearerAuthTemplate;
break;
case 'ApiKeyAuth':
newSchema = apiKeyAuthTemplate;
break;
case 'openId':
newSchema = openIdAuthTemplate;
break;
case 'oAuthSample':
newSchema = oauth2AuthTemplate;
break;
case 'cookieAuth':
newSchema = cookieAuthTemplate;
break;
default:
newSchema = undefined;
break;
}
if (newSchema) {
tempSchemaList = [...tempSchemaList, newSchema];
$openApiStore.securitySchemas = tempSchemaList;
}
};
const removeSecuritySchema = (index: number) => {
let tempSchemaList = $openApiStore.securitySchemas;
tempSchemaList.splice(index, 1);
$openApiStore.securitySchemas = tempSchemaList;
};
</script>
<form class="container mx-auto card px-6 py-4 space-y-4">
{#each $openApiStore.securitySchemas as schema, index}
<AuthenticationItem bind:data={schema} />
<span class="flex justify-center">
<button
type="button"
class="btn variant-ringed-error hover:variant-filled-error"
on:click={() => {
removeSecuritySchema(index);
}}
>
Remove Security Schema
</button>
</span>
<hr />
{/each}
<span class="flex justify-center items-center gap-2 max-w-sm mx-auto">
<select name="security-schema" bind:value={selectedSchema} class="input">
<option value="basicAuth" selected>Basic Auth</option>
<option value="bearerAuth">Bearer Auth</option>
<option value="ApiKeyAuth">API Key Auth</option>
<option value="openId">OpenID</option>
<option value="oAuthSample">OAuth2</option>
<option value="cookieAuth">Cookie Auth</option>
</select>
<button type="button" class="btn variant-filled-primary" on:click={addSecuritySchema}>
Add Security Schema
</button>
</span>
</form>

View File

@@ -1,9 +1,12 @@
import type { OpenAPI } from './types';
import { persisted } from 'svelte-persisted-store';
export const openApiStore = persisted<OpenAPI>('openApi', {
export const localStoragePrefix = 'openapigen-';
export const openApiStore = persisted<OpenAPI>(`${localStoragePrefix}openApi`, {
title: '',
version: '',
description: '',
servers: []
servers: [],
securitySchemas: []
});

View File

@@ -1,14 +0,0 @@
export interface OpenAPI extends Info {
servers: Server[];
}
export interface Info {
title: string;
version: string;
description?: string;
}
export interface Server {
url: string;
description?: string;
}

81
src/lib/types/auth.ts Normal file
View File

@@ -0,0 +1,81 @@
// Basic Auth
export interface BasicAuth extends Auth {
type: 'http';
scheme: 'basic';
}
// Bearer Token
export interface BearerAuth extends Auth {
type: 'http';
scheme: 'bearer';
bearerFormat?: string; // arbitrary value for documentation purposes
}
// API Key
export interface ApiKeyAuth extends Auth {
type: 'apiKey';
in: 'header' | 'query' | 'cookie';
name: string;
}
// OAuth2
export interface OAuth2Auth extends Auth {
type: 'oauth2';
description: string;
flows: Flows[];
}
type Scope = [
{
scope: string;
description?: string;
}
];
export type Flows =
| OAuth2AuchorizationCodeFlow
| OAuth2ImplicitFlow
| OAuth2PasswordFlow
| OAuth2ClientCredentialsFlow;
interface OAuth2AuchorizationCodeFlow {
name: 'authorizationCode';
authorizationUrl: string;
tokenUrl: string;
scopes: Scope[];
}
interface OAuth2ImplicitFlow {
name: 'implicit';
authorizationUrl: string;
scopes: Scope[];
}
interface OAuth2PasswordFlow {
name: 'password';
tokenUrl: string;
scopes: Scope[];
}
interface OAuth2ClientCredentialsFlow {
name: 'clientCredentials';
tokenUrl: string;
scopes: Scope[];
}
// OpenID Connect
export interface OpenIdConnectAuth extends Auth {
type: 'openIdConnect';
openIdConnectUrl: string;
}
// Cookie Auth
export interface CookieAuth extends Auth {
type: 'apiKey';
in: 'cookie';
name: string;
}
interface Auth {
identifier: string; // a unique name for the auth-configuration
}

32
src/lib/types/index.ts Normal file
View File

@@ -0,0 +1,32 @@
import type {
ApiKeyAuth,
BasicAuth,
BearerAuth,
CookieAuth,
OAuth2Auth,
OpenIdConnectAuth
} from './auth';
export interface OpenAPI extends Info {
servers: Server[];
securitySchemas: SecuritySchema[];
}
export interface Info {
title: string;
version: string;
description?: string;
}
export interface Server {
url: string;
description?: string;
}
export type SecuritySchema =
| BasicAuth
| BearerAuth
| ApiKeyAuth
| OAuth2Auth
| OpenIdConnectAuth
| CookieAuth;

View File

@@ -3,6 +3,7 @@
import Info from '$lib/components/sections/Info.svelte';
import { TabGroup, Tab } from '@skeletonlabs/skeleton';
import { persisted } from 'svelte-persisted-store';
import Authentication from '$lib/components/sections/Authentication.svelte';
// let tabSet: number = 0;
const tabSet = persisted<number>('tabSet', 0);
@@ -13,7 +14,7 @@
<h4 class="h4">Info</h4>
</Tab>
<Tab bind:group={$tabSet} name="authentication" value={1}>
<h4 class="h4">Authentication</h4>
<h4 class="h4">Authentication Schemas</h4>
</Tab>
<Tab bind:group={$tabSet} name="servers" value={2}>
<h4 class="h4">Servers</h4>
@@ -27,17 +28,13 @@
<svelte:fragment slot="panel">
{#if $tabSet === 0}
<Info></Info>
{/if}
{#if $tabSet === 1}
<p>Authentication</p>
{/if}
{#if $tabSet === 2}
{:else if $tabSet === 1}
<Authentication />
{:else if $tabSet === 2}
<Servers />
{/if}
{#if $tabSet === 3}
{:else if $tabSet === 3}
<p>Paths</p>
{/if}
{#if $tabSet === 4}
{:else if $tabSet === 4}
<p>Components</p>
{/if}
</svelte:fragment>