Enhance ticket creation api and UI to support ticket expiry (#957)

Ticket expiry was already supported on core but no way to defined it,
neither from UI neither from API

Changed API to accept new optional field `expiry` and update UI form to
be able to set it from UI

closes #924
This commit is contained in:
Thibaud Lepretre 2024-03-01 19:27:48 +01:00 committed by GitHub
parent 4a833c5559
commit 257fb38a21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 38 additions and 5 deletions

View file

@ -96,6 +96,10 @@ The binary is in `target/{debug|release}`.
* Svelte
* Bootstrap
### Backend API
* Warpgate admin and user facing APIs use autogenerated OpenAPI schemas and SDKs. To update the SDKs after changing the query/response structures, run `just openapi-all`.
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

View file

@ -1,6 +1,7 @@
use std::sync::Arc;
use anyhow::Context;
use chrono::{DateTime, Utc};
use poem::web::Data;
use poem_openapi::payload::Json;
use poem_openapi::{ApiResponse, Object, OpenApi};
@ -23,6 +24,7 @@ enum GetTicketsResponse {
struct CreateTicketRequest {
username: String,
target_name: String,
expiry: Option<DateTime<Utc>>
}
#[derive(Object)]
@ -84,7 +86,7 @@ impl Api {
username: Set(body.username.clone()),
target: Set(body.target_name.clone()),
created: Set(chrono::Utc::now()),
expiry: Set(None),
expiry: Set(body.expiry),
..Default::default()
};

View file

@ -12,6 +12,7 @@ let targets: Target[]|undefined
let users: User[]|undefined
let selectedTarget: Target|undefined
let selectedUser: User|undefined
let selectedExpiry: string|undefined
let result: TicketAndSecret|undefined
async function load () {
@ -37,6 +38,7 @@ async function create () {
createTicketRequest: {
username: selectedUser.username,
targetName: selectedTarget.name,
expiry: selectedExpiry ? new Date(selectedExpiry) : undefined,
},
})
} catch (err) {
@ -75,6 +77,7 @@ async function create () {
use:link
>Done</a>
{:else}
<div class="narrow-page">
<div class="page-summary-bar">
<h1>Create an access ticket</h1>
</div>
@ -103,8 +106,13 @@ async function create () {
</FormGroup>
{/if}
<FormGroup floating label="Expiry (optional)">
<input type="datetime-local" bind:value={selectedExpiry} class="form-control"/>
</FormGroup>
<AsyncButton
outline
click={create}
>Create ticket</AsyncButton>
</div>
{/if}

View file

@ -3,6 +3,8 @@ import { api, Ticket } from 'admin/lib/api'
import { link } from 'svelte-spa-router'
import { Alert } from 'sveltestrap'
import RelativeDate from './RelativeDate.svelte'
import Fa from 'svelte-fa'
import { faCalendarXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons'
let error: Error|undefined
let tickets: Ticket[]|undefined
@ -45,10 +47,15 @@ async function deleteTicket (ticket: Ticket) {
<div class="list-group list-group-flush">
{#each tickets as ticket}
<div class="list-group-item">
<strong class="me-auto">
<strong>
Access to {ticket.target} as {ticket.username}
</strong>
<small class="text-muted me-4">
{#if ticket.expiry}
<small class="text-muted ms-4">
<Fa icon={ticket.expiry > new Date() ? faCalendarCheck : faCalendarXmark} fw /> Until {ticket.expiry?.toLocaleString()}
</small>
{/if}
<small class="text-muted me-4 ms-auto">
<RelativeDate date={ticket.created} />
</small>
<a href={''} on:click|preventDefault={() => deleteTicket(ticket)}>Delete</a>

View file

@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "Warpgate Web Admin",
"version": "0.7.4"
"version": "0.9.1"
},
"servers": [
{
@ -1123,6 +1123,10 @@
},
"target_name": {
"type": "string"
},
"expiry": {
"type": "string",
"format": "date-time"
}
}
},

View file

@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "Warpgate HTTP proxy",
"version": "0.7.4"
"version": "0.9.1"
},
"servers": [
{

View file

@ -1,3 +1,5 @@
@use "sass:map";
// Layout & components
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@ -101,3 +103,9 @@ input:-webkit-autofill:focus {
.form-floating>.form-control:not(:focus)::placeholder {
color: transparent;
}
@include media-breakpoint-up(md) {
.narrow-page {
width: map.get($grid-breakpoints, "md");
}
}