Skip to content

Commit

Permalink
Merge pull request #452 from dfpc-coe/video-lease-channels
Browse files Browse the repository at this point in the history
Video Lease Channels
  • Loading branch information
ingalls authored Dec 18, 2024
2 parents 92e2441 + 232db22 commit 9675e32
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 40 deletions.
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

0 comments on commit 9675e32

Please sign in to comment.