Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video Lease Channels #452

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions api/lib/control/video-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Config from '../config.js';
import { Type, Static } from '@sinclair/typebox';
import { VideoLeaseResponse } from '../types.js';
import fetch from '../fetch.js';
import TAKAPI, { APIAuthCertificate } from '../tak-api.js';

export const Protocols = Type.Object({
rtmp: Type.Optional(Type.Object({
Expand Down Expand Up @@ -361,6 +362,29 @@ export default class VideoServiceControl {
return lease;
}

async from(
leaseid: string,
opts: {
username: string
admin: boolean
}
): Promise<Static<typeof VideoLeaseResponse>> {
const lease = await this.config.models.VideoLease.from(leaseid);

if (!opts.admin) {
const profile = await this.config.models.Profile.from(opts.username);
const api = await TAKAPI.init(new URL(String(this.config.server.api)), new APIAuthCertificate(profile.auth.cert, profile.auth.key));
const groups = (await api.Group.list({ useCache: true }))
.data.map((group) => group.name);

if (lease.username !== opts.username && (!lease.channel || !groups.includes(lease.channel))) {
throw new Err(400, null, 'You can only access a lease you created or that is assigned to a channel you are in');
}
}

return lease;
}

async commit(
leaseid: string,
body: {
Expand All @@ -376,15 +400,7 @@ export default class VideoServiceControl {
const video = await this.settings();
if (!video.configured) throw new Err(400, null, 'Media Integration is not configured');

let lease = await this.config.models.VideoLease.from(leaseid);

if (opts.admin) {
lease = await this.config.models.VideoLease.commit(leaseid, body);
} else if (lease.username === opts.username) {
lease = await this.config.models.VideoLease.commit(leaseid, body);
} else {
throw new Err(400, null, 'You can only update a lease you created');
}
const lease = await this.from(leaseid, opts);

try {
await this.path(lease.path);
Expand Down Expand Up @@ -435,14 +451,20 @@ export default class VideoServiceControl {
}
}

async delete(leaseid: string): Promise<void> {
async delete(
leaseid: string,
opts: {
username: string;
admin: boolean;
}
): Promise<void> {
const video = await this.settings();

if (!video.configured) throw new Err(400, null, 'Media Integration is not configured');

const headers = this.headers(video.username, video.password);

const lease = await this.config.models.VideoLease.from(leaseid);
const lease = await this.from(leaseid, opts);

await this.config.models.VideoLease.delete(leaseid);

Expand Down
38 changes: 16 additions & 22 deletions api/routes/video-lease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { randomUUID } from 'node:crypto';
import { StandardResponse, VideoLeaseResponse } from '../lib/types.js';
import ECSVideoControl, { Protocols } from '../lib/control/video-service.js';
import * as Default from '../lib/limits.js';
import TAKAPI, { APIAuthCertificate } from '../lib/tak-api.js';

export default async function router(schema: Schema, config: Config) {
const videoControl = new ECSVideoControl(config);
Expand Down Expand Up @@ -55,14 +56,20 @@ export default async function router(schema: Schema, config: Config) {
} else {
const user = await Auth.as_user(config, req);

const profile = await config.models.Profile.from(user.email);
const api = await TAKAPI.init(new URL(String(config.server.api)), new APIAuthCertificate(profile.auth.cert, profile.auth.key));

const groups = (await api.Group.list({ useCache: true }))
.data.map((group) => group.name);

res.json(await config.models.VideoLease.list({
limit: req.query.limit,
page: req.query.page,
order: req.query.order,
sort: req.query.sort,
where: sql`
name ~* ${req.query.filter}
AND username = ${user.email}
AND (username = ${user.email} OR channel IN ${groups})
AND ephemeral = ${req.query.ephemeral}
`
}));
Expand All @@ -87,16 +94,10 @@ export default async function router(schema: Schema, config: Config) {
try {
const user = await Auth.as_user(config, req);

let lease;
if (user.access === AuthUserAccess.ADMIN) {
lease = await config.models.VideoLease.from(req.params.lease);
} else {
lease = await config.models.VideoLease.from(req.params.lease);

if (lease.username !== user.email) {
throw new Err(400, null, 'You can only delete a lease you created');
}
}
const lease = await videoControl.from(req.params.lease, {
username: user.email,
admin: user.access === AuthUserAccess.ADMIN
});

res.json({
lease,
Expand Down Expand Up @@ -233,17 +234,10 @@ export default async function router(schema: Schema, config: Config) {
try {
const user = await Auth.as_user(config, req);

if (user.access === AuthUserAccess.ADMIN) {
await videoControl.delete(req.params.lease);
} else {
const lease = await config.models.VideoLease.from(req.params.lease);

if (lease.username === user.email) {
await videoControl.delete(req.params.lease);
} else {
throw new Err(400, null, 'You can only delete a lease you created');
}
}
await videoControl.delete(req.params.lease, {
username: user.email,
admin: user.access === AuthUserAccess.ADMIN
});

res.json({
status: 200,
Expand Down
2 changes: 1 addition & 1 deletion api/web/src/components/Admin/Videos/AdminVideoLeases.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const list = ref<VideoLeaseList>({
items: []
});

watch(paging, async () => {
watch(paging.value, async () => {
await fetchList();
});

Expand Down
34 changes: 28 additions & 6 deletions api/web/src/components/CloudTAK/Menu/Videos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
label='Video Connections'
:create='false'
/>
<TablerAlert
v-else-if='error'
:err='error'
/>
<template v-else>
<div
v-for='connection in connections.videoConnections'
Expand Down Expand Up @@ -131,6 +135,10 @@
label='Video Leases'
:create='false'
/>
<TablerAlert
v-else-if='error'
:err='error'
/>
<div
v-for='l in leases.items'
v-else
Expand Down Expand Up @@ -199,6 +207,7 @@ import { useCOTStore } from '../../../stores/cots.ts';
import { useVideoStore } from '../../../stores/videos.ts';
import {
TablerNone,
TablerAlert,
TablerDelete,
TablerIconButton
} from '@tak-ps/vue-tabler';
Expand All @@ -215,6 +224,7 @@ const cotStore = useCOTStore();
const videoStore = useVideoStore();

const mode = ref('connections');
const error = ref<Error | undefined>();
const loading = ref(true);
const lease = ref();
const leases = ref<VideoLeaseList>({ total: 0, items: [] });
Expand All @@ -241,16 +251,28 @@ function expired(expiration: string | null): boolean {
}

async function fetchLeases(): Promise<void> {
lease.value = undefined;
loading.value = true;
leases.value = await std('/api/video/lease') as VideoLeaseList
try {
lease.value = undefined;
loading.value = true;
error.value = undefined;
leases.value = await std('/api/video/lease') as VideoLeaseList
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err));
}

loading.value = false;
}

async function fetchConnections(): Promise<void> {
lease.value = undefined;
loading.value = true;
connections.value = await std('/api/marti/video') as VideoConnectionList;
try {
lease.value = undefined;
loading.value = true;
error.value = undefined;
connections.value = await std('/api/marti/video') as VideoConnectionList;
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err));
}

loading.value = false;
}

Expand Down
Loading