mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-19 08:41:24 +01:00
Add authenticated media certificate generator
This commit is contained in:
parent
be14f5bddc
commit
d6fe411443
14 changed files with 1930 additions and 11 deletions
161
auth-media-cert-gen/src/lib.rs
Normal file
161
auth-media-cert-gen/src/lib.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use time::OffsetDateTime;
|
||||
use typst::{
|
||||
diag::{FileError, FileResult},
|
||||
eval::Tracer,
|
||||
foundations::{Bytes, Datetime, IntoValue},
|
||||
syntax::{FileId, Source, VirtualPath},
|
||||
text::{Font, FontBook},
|
||||
visualize::Color,
|
||||
Library, World,
|
||||
};
|
||||
|
||||
const EMBEDDED_FONTS: &[&[u8]] = &[
|
||||
include_bytes!("../res/ComicNeue-Bold.ttf"),
|
||||
include_bytes!("../res/NotoColorEmoji.ttf"),
|
||||
];
|
||||
|
||||
const AUTH_SOURCE: &str = include_str!("../res/auth.typ");
|
||||
const UNAUTH_SOURCE: &str = include_str!("../res/unauth.typ");
|
||||
|
||||
const FILE_SOURCES: &[(&str, &[u8])] = &[
|
||||
("confetti.svg", include_bytes!("../res/confetti.svg")),
|
||||
("party.svg", include_bytes!("../res/party.svg")),
|
||||
];
|
||||
|
||||
fn get_fonts() -> (FontBook, Vec<Font>) {
|
||||
let mut book = FontBook::new();
|
||||
let mut fonts = vec![];
|
||||
for font in EMBEDDED_FONTS
|
||||
.iter()
|
||||
.flat_map(|data| Font::iter(Bytes::from_static(data)))
|
||||
{
|
||||
book.push(font.info().clone());
|
||||
fonts.push(font);
|
||||
}
|
||||
|
||||
(book, fonts)
|
||||
}
|
||||
|
||||
/// A minimal world that generates Authenticated Media certificates
|
||||
pub struct AuthMediaWorld {
|
||||
/// The name of the server issuing the certificate
|
||||
issuer: String,
|
||||
/// File ID of the authenticated source
|
||||
auth_id: FileId,
|
||||
/// File ID of the unauthenticated source
|
||||
unauth_id: FileId,
|
||||
/// True if the authenticated source should be used
|
||||
is_authenticated: bool,
|
||||
/// Typst's standard library.
|
||||
library: Prehashed<Library>,
|
||||
/// Metadata about discovered fonts.
|
||||
book: Prehashed<FontBook>,
|
||||
/// Locations of and storage for lazily loaded fonts.
|
||||
fonts: Vec<Font>,
|
||||
/// Maps file ids to source files and buffers.
|
||||
slots: HashMap<FileId, (Bytes, Option<Source>)>,
|
||||
/// The current datetime if requested. This is stored here to ensure it is
|
||||
/// always the same within one compilation. Reset between compilations.
|
||||
now: Datetime,
|
||||
}
|
||||
|
||||
impl AuthMediaWorld {
|
||||
#[must_use]
|
||||
pub fn new(issuer: String) -> Self {
|
||||
let auth_id = FileId::new_fake(VirtualPath::new("<auth>"));
|
||||
let unauth_id = FileId::new_fake(VirtualPath::new("<unauth>"));
|
||||
|
||||
let mut slots = HashMap::new();
|
||||
for &(name, data) in FILE_SOURCES {
|
||||
let id = FileId::new(None, VirtualPath::new(format!("/{name}")));
|
||||
let bytes = data.into();
|
||||
let source = std::str::from_utf8(data)
|
||||
.ok()
|
||||
.map(|s| Source::new(id, s.to_owned()));
|
||||
slots.insert(id, (bytes, source));
|
||||
}
|
||||
|
||||
let library = Library::builder().build();
|
||||
|
||||
let (book, fonts) = get_fonts();
|
||||
let now = Datetime::Date(OffsetDateTime::now_utc().date());
|
||||
|
||||
Self {
|
||||
issuer,
|
||||
auth_id,
|
||||
unauth_id,
|
||||
is_authenticated: false,
|
||||
library: Prehashed::new(library),
|
||||
book: Prehashed::new(book),
|
||||
fonts,
|
||||
slots,
|
||||
now,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&mut self, target: Option<&str>) -> Option<Vec<u8>> {
|
||||
let mut inputs =
|
||||
vec![("issuer".into(), self.issuer.as_str().into_value())];
|
||||
if let Some(target) = target {
|
||||
inputs.push(("target".into(), target.into_value()));
|
||||
}
|
||||
let library = Library::builder()
|
||||
.with_inputs(inputs.into_iter().collect())
|
||||
.build();
|
||||
self.library.update(|old| *old = library);
|
||||
self.is_authenticated = target.is_some();
|
||||
|
||||
let mut tracer = Tracer::new();
|
||||
let document = typst::compile(self, &mut tracer).unwrap();
|
||||
if document.pages.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
typst_render::render(&document.pages[0].frame, 2.0, Color::WHITE)
|
||||
.encode_png()
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl World for AuthMediaWorld {
|
||||
fn library(&self) -> &Prehashed<Library> {
|
||||
&self.library
|
||||
}
|
||||
|
||||
fn book(&self) -> &Prehashed<FontBook> {
|
||||
&self.book
|
||||
}
|
||||
|
||||
fn main(&self) -> Source {
|
||||
if self.is_authenticated {
|
||||
Source::new(self.auth_id, AUTH_SOURCE.to_owned())
|
||||
} else {
|
||||
Source::new(self.unauth_id, UNAUTH_SOURCE.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||
let (_bytes, source) = self.slots.get(&id).ok_or(
|
||||
FileError::NotFound(id.vpath().as_rooted_path().to_owned()),
|
||||
)?;
|
||||
Ok(source.as_ref().ok_or(FileError::NotSource)?.clone())
|
||||
}
|
||||
|
||||
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
||||
let (bytes, _source) = self.slots.get(&id).ok_or(
|
||||
FileError::NotFound(id.vpath().as_rooted_path().to_owned()),
|
||||
)?;
|
||||
Ok(bytes.clone())
|
||||
}
|
||||
|
||||
fn font(&self, index: usize) -> Option<Font> {
|
||||
self.fonts.get(index).cloned()
|
||||
}
|
||||
|
||||
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
|
||||
assert_eq!(offset, None, "today offset not supported");
|
||||
Some(self.now)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue