Commit Graph

1347 Commits

Author SHA1 Message Date
Eric Bailey 27882035e7 Use uppercase 2fa codes in email templates (#2843)
* Update email templates

* Format random readme
2024-09-30 17:18:33 -05:00
github-actions[bot] 6593fdc3f4 Version packages (#2812)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-27 13:04:34 -05:00
Samuel Newman 2676206e42 Pinned posts (#2771)
* pinned posts lexicon

* codegen

* change lexicon, different approach

* codegen 2

* dataplane db migration

* move pinned post lexicon to right place

* add pinned posts optionally to getAuthorFeed

* remove type modification

* Clarify naming, add viewer state, add tests

* return pinnedPost with profileViewDetailed

* allow pinned replies in `posts_and_author_threads`

* clearer variable naming

* annotate type of `items`

* boolean --> varchar

* reuse authorDid in viewerPinned

* simplify test

* make pinned post not top post in test

* update snapshot

* changeset

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: dholms <dtholmgren@gmail.com>
2024-09-26 18:26:45 -05:00
Matthieu Sieben b298bfd280 Prevent PDS crash when catchall proxy request are cancelled (#2824)
* Prevent PDS crash when catchall proxy request are cancelled

* fixes

* ensure stream destruction
2024-09-24 11:55:29 -05:00
Matthieu Sieben a07b21151f PDS pipethrough optimizations (#2770)
* Micro optimization in request proxying

* Request NSID parsing optimization

* DID document parsing optimization

* remove un-necessary call to next()

* Allow HandlerPipeThrough to be used with streams

* Refactor pipethrough to work with streams

* Expose "unicastLookup" DNS lookup and "isUnicastIp" utilities

* Use a hardened, HTTP2 compatible, client to perform proxied requests

* changeset

* tidy

* Properly handle compressed streams

* tidy

* update @types/node

* refactor

* Improved error management

* Expose parseContentEncoding() util

* use pipeline from nodejs

* Avoid decoding in read-after-write (if possible)

* Various fixes

* Return Buffer instance from streamToBytes

* fixes

* Add omit() utility

* tidy

* lint

* typo

* Use Buffer instead of ArrayBuffer form pipe through handler result

* optimization

* tidy

* refactor

* increase highWaterMark

* remove un-necessary type check

* Use undici.request where more relevant

* Improve soc in fetch utils

* feedback

* fidy

* tidy

* test refactor

* safer fetch

* changeset

* expose and re-use extractUrl util

* small optimizations

* tidy

* optimization

* build branch

---------

Co-authored-by: dholms <dtholmgren@gmail.com>
2024-09-19 18:24:20 -05:00
Eric Bailey 319aa7cf6d Add isFallback to getSuggestedFollowsByActor method (#2805)
* Add isFallback to `getSuggestedFollowsByActor`

Inferred based on returned `relativeToDid` from the suggestions
response.

* Integrate new params

* Fix logic
2024-09-13 12:37:15 -05:00
Eric Bailey a06634ae57 Email templates at (#2813)
* Add @ to handles, update PLC copy

* Changeset

* lint

---------

Co-authored-by: dholms <dtholmgren@gmail.com>
2024-09-12 11:57:41 -05:00
Eric Bailey 922b94ce37 Update email templates (#2767)
* Update email templates

* Update PLC

* Update test with new email string

* Format

* One more test update

* Use handle instead of identifier to match entryway

* Changeset
2024-09-12 10:53:31 -05:00
github-actions[bot] 85c85350d1 Version packages (#2791)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-11 18:30:47 -05:00
Eric Bailey 33aa0c7222 NUX API (#2810)
* Codegen

* Explicitly add Zod (already a peer dep) and validation to api

* Add Nux methods

* Match naming convention

* Remove id, it won't be used

* Add tests

* Use id instead of name, little clearer

* Update API contracts

* Update tests

* Changeset

* Don't mutate
2024-09-11 18:25:05 -05:00
Foysal Ahamed 62a8225f36 Optionally ack all open subjects from the author with takedown event and get all mod subjects of a user (#2793)
*  Add acknowledgeAllSubjectsOfAccount flag with takedown event

* 📝 better documentation of forAccount and subject params

* 🧹 Cleanup tests

*  Change wording according to review

*  Refactor tests

* ♻️ Rename acknowledge flag
2024-09-11 18:26:53 +02:00
Daniel Holmgren bcefbdbc6b Slow down get repos (#2802)
* slow down get repos

* move wait
2024-09-08 20:32:55 -05:00
github-actions[bot] 0a37a3cc56 Version packages (#2788)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-04 20:22:22 -05:00
Daniel Holmgren b15dec2f4f Atproto sync package (#2752)
* first pass/port

* reworking

* authenticated commit parsing

* authenticate identity evts

* some testing

* tidy & add firehose to queue

* error handling

* fix test

* refactor sync queue + some tests

* fix race in sync queue

* rm firehose from syncqueue

* add tests for queue utils

* README

* lint readme

* filter before parsing

* pr feedback

* small fix

* changesets

* fix type

* Rework dataplane subscription (#2766)

* working sync package into appview subscription

* add restart method to subscription for tests

* fix another test

* tidy subscription utils/files

* remove dupe property

* tidy after merge

* fix start cursor on subscription

* tweak process full subscription logic

* fixes
2024-09-04 20:18:16 -05:00
github-actions[bot] 71305e8595 Version packages (#2764)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-04 14:23:00 +02:00
Foysal Ahamed e4d41d66fa ❇️ Template language (#2780)
*  Throw specific error for duplicate template name

* 🧹 Cleanup console

*  Throw duplicate template name error from update too

*  Add language to templates

* 📝 Add changeset

*  Add missing event type

*  Add language format in lexicon and error checker in util

* 🚨 fix linter issues
2024-09-04 12:42:39 +02:00
Daniel Holmgren bfbac24312 Repo write commit metadata (#2745)
* schemas & codegen

* impl

* bundle commit & rev
2024-08-30 13:18:43 -05:00
devin ivy befebc0e98 Revise repo write validation for unknown Lexicons (#2748)
* lexicon: validation status result from repo writes

* pds: return  validation status from repo writes, write results from applyWrites

* tidy
2024-08-30 12:59:55 -05:00
github-actions[bot] 6bc7faf087 Version packages (#2761)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-28 19:13:54 -04:00
devin ivy 80ada8f476 Video lexicons and appview views (#2751)
* lexicon: initial lexicons for video embeds in bsky app

* lexicon: fix video caption file size limit

* codegen

* appview: stub out video embed view logic

* api prerelease

* api prerelease

* lexicon: video upload/processing lexicons

* tidy

* lexicon: app.bsky.video lexicons for uploads

* codegen

* api prerelease

* appview: present video embeds on posts

* appview: snaps

* changeset

* appview: fix wiring of video url config
2024-08-28 19:03:35 -04:00
github-actions[bot] e49e05c0fa Version packages (#2750)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 17:35:48 -04:00
github-actions[bot] a1d8c77edd Version packages (#2738)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 14:03:39 -04:00
Matthieu Sieben ebb318325b Improved control over JWT's typ claim (#2743)
* Add "jwtAlg" option to verifySignature() function

* Verify service JWT header values. Add iat claim to service JWT

* Allow missing 'typ' claim in service auth jwt

* Add, and verify, a "typ" header to access and refresh tokens

* tidy

* Properly identify JWT typ missmatch

* tidy

* exclude known invalid "typ" from service auth headers

* tidy

* tidy changeset

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
2024-08-27 13:50:14 -04:00
Matthieu Sieben dee817b6e0 OAuth: Add authorization scopes & remove OpenID compatibility (#2734)
* Re-use code definition of oauthResponseTypeSchema

* Generate proper invalid_authorization_details

* Remove OpenID compatibility

* tidy

* properly verify presence of jti claim in client assertion

* Remove non-standard "sub" from OAuthTokenResponse

* Remove nonce from authorization request

* tidy

* Enforce uniqueness of code_challenge

* remove unused "atproto" scope

* Improve reporting of validation errors

* Allow empty set of scopes

* Do not remove scopes not advertised in the AS's "scopes_supported" when building the authorization request.

* Prevent empty scope string

* Remove invalid check from token response

* remove un-necessary session refresh

* Validate scopes characters according to OAuth 2.1 spec

* Mandate the use of "atproto" scope

* Disable ability to list app passwords when using an app password

* Use locally defined authPassthru in com.atproto.admin.* handlers

* provide proper production handle resolver in example

* properly compote login method

* feat(oauth-provider): always rotate session cookie on sign-in

* feat(oauth-provider): do not require consent from first party apps

* update request parameter's prompt before other param validation checks

* feat(oauth-provider): rework display of client name

* feat(oauth-client-browser:example): add token info introspection

* feat(oauth-client-browser:example): allow defining scope globally

* Display requested scopes during the auth flow

* Add, and verify, a "typ" header to access and refresh tokens

* Ignore case when checking for dpop auth scheme

* Add "jwtAlg" option to verifySignature() function

* Verify service JWT header values. Add iat claim to service JWT

* Add support for "transition:generic" and "transition:chat.bsky" oauth scopes in PDS

* oauth-client-browser(example): add scope request

* Add missing "atproto" scope

* Allow missing 'typ' claim in service auth jwt

* Improved 401 feedback

Co-authored-by: devin ivy <devinivy@gmail.com>

* Properly parse scopes upon verification

Co-authored-by: devin ivy <devinivy@gmail.com>

* Rename "atp" to "credential" auth in oauth-client-browser example

* add key to iteration items

* Make CORS protection stronger

* Allow OAuthProvider to define its own CORS policies

* Revert "Allow missing 'typ' claim in service auth jwt"

This reverts commit 15c6b9e2197064eb5de61a96de6497060edb824e.

* Revert "Verify service JWT header values. Add iat claim to service JWT"

This reverts commit 08df8df322a3f4b631c4a63a61d55b2c84c60c11.

* Revert "Add "jwtAlg" option to verifySignature() function"

This reverts commit d0f77354e6904678e7f5d76bb026f07537443ba9.

* Revert "Add, and verify, a "typ" header to access and refresh tokens"

This reverts commit 3e21be9e4b5875caa5e862c11f2196786fb2366d.

* pds: implement protected service auth methods

* Prevent app password management using sessions initiated from an app password.

* Alphabetically sort PROTECTED_METHODS

* Revert changes to app password management permissions

* tidy

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
2024-08-27 13:43:29 -04:00
Eric Bailey a8e1f9000d Return ThreadgateView on response from getPostThread (#2737)
* Return `ThreadgateView` on response from `getPostThread`

* Changeset

* Format

* Add to test

* Clean up logic

* Use suggestion from Dan
2024-08-22 17:00:01 -05:00
github-actions[bot] f70bd6a9dc Version packages (#2736)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-22 09:54:41 -07:00
Hailey 4ab2483547 Add quoteCount to embed view (#2735)
Co-authored-by: devin ivy <devinivy@gmail.com>
2024-08-22 09:49:06 -07:00
Matthieu Sieben 5fef534610 codegen 2024-08-22 11:55:36 +02:00
github-actions[bot] 1572058887 Version packages (#2732)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-21 14:42:01 -05:00
Eric Bailey aba664fbdf Detached QPs and hidden replies (#2675)
* Add new postgate lex, hiddeReplies to threadgate, codegen

* Add protobufs

* Add to mock dataplane

* Add matching postgate method to feed hydration methods

* Add to getRecord

* Add to HydrationState

* Fix typo

* Add to mergeStates, fetch embeds in threads

* Integrate into embed views

* Add test for QPs in threads

* Add feed test

* Fix naming convention in protos

* Add #viewRemoved record view, rename postgate.json

* Integrate new view

* Filter hidden replies from feeds

* Filter out replies at the handler level, do not filter for author feeds

* Fix lint

* Move hidden reply check to view layer

* Reduce, reuse, recycle

* Rename to lowercase

* Rename layer vars

* Add quote gate props to postgate (#2693)

* Add quote gate props to postgate

* Consistent naming

* Fix record structure

* Codegen

* Show hidden replies in author feed

* Allow reposts of hidden replies

* Lex and codegen

* Add violates_quote_gate to proto

* Consistent naming, codegen

* Integrate violatesQuotegate and canQuotepost

* Remove rules, codegen

* Hydrate all postgates for all requested posts

* Match other impl

* Add test, need to split these out

* Format

* Hydrate first nested embeds too

* Add postgate test suite

* Add violatesQuoteGate to dataplane

* Ingest and set violatesQuoteGate, return on meta

* Return removed embed for quotes that violate gate

* Add test

* Dedupe URIs before fetching postgates

* Update snaps

* Snap

* Format

* Updating naming conventions for postgate-related attributes

* Correct naming

* Consistency

* Proto too

* Rename to viewDetached

* Codegen

* Rename everything

* Codegen

* Quotes that violate a quote gate can still be quoted themselves

* Couple more renames

* Snaps

* Ensure reply ref is tombstoned for hidden replies

* Split out hidden replies tests and create fresh fixture

* Hydrate threadgates for reply notifications, filter hidden replies

* Remove snap

* Add flaky test

* Rename violatesEmbeddingRules

* Fix flaky test

* Only write to db if violatesEmbeddingRules is true

* DRY up post uri -> gate uri logic

* isThreadgateListRule

* Don't share users object between tests

* No pascal

* Remove default params

* Find -> some

* canQuotepost -> canEmbed, remove optional

* Fix quoteee typo

* await follows

* Throw in post uri -> gate utils

* Ensure fetch threadgates for reply roots

* Don't hydrate threadgates twice

* DRY up uri -> did parsing

* Clean up parsePostgate logic

* Format

* Revert change

* Revert change

* Replace a couple more uri->did conversions

* Only filter replies from feeds if viewer hid them

* Revert, filter out replies that are hidden from feeds

* Remove old test

* Replace uri->did util

* Revert change to unused file

* Only validatePostEmbed and check postgates for post records

* Ensure notifications aren't generated down a hidden reply chain

* Changeset

* Cleanup

* Fix notification filtering logic

* Simplify

* Don't notify for invalid embeds

* Use new APIs

* Add hasPostGate and hasThreadGate flags from dataplane

* Only fetch postgates if post has one

* Only fetch threadgates if post has one or was deleted

* Remove notification filtering

* Don't hydrate threadgates for notifications

* Move hidden replies in feeds to match block handling

* Do no filtering of hidden replies in feeds

* Revert "Don't hydrate threadgates for notifications"

This reverts commit 1dcec0b239a7b9d6800427b26b8ba3e6a54210f9.

* Revert "Remove notification filtering"

This reverts commit 1e7069dfd809d1f18e9f05fd1d422e7399aa1bb0.

* Filter notifications for OP only

* Add additional check to hidden replies test

* Move noty filter logic into method handler

* Update .changeset/perfect-parrots-appear.md

Co-authored-by: devin ivy <devinivy@gmail.com>

* Update packages/bsky/tests/seed/postgates.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* Another structuredClone

* Update packages/bsky/src/hydration/hydrator.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* Better comment

* Update packages/bsky/src/data-plane/server/indexing/plugins/post.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* Regen protos to match dataplane

* Update quotes snap to include embeddingDisabled

* Clarify usage of post uri -> gate utils

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
2024-08-21 14:36:51 -05:00
Hailey 2a0c088cc5 quote aggs, list quotes (#2658)
* add quote count to post_agg, add getPostQuotes

rework schema

rework schema

add getPostQuotes to api

use posts

use posts

codegen

use items instead of quotes

codegen

add getPostQuotes

add quoteCount to response

update lexicon for postview

increment post ags

add quote to post aggs

add quote interface

oops

add quote table migration

* update

* bufgen

* update params

* update to use v2

* logs

* rm comment

* pass cursor

* add index

* Update packages/bsky/src/data-plane/server/db/migrations/20240723T220703655Z-quotes.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* only if its a post

* tests

* Discard changes to packages/bsky/tests/views/posts.test.ts

* fix client call

* Include new quotes agg in test expectation

* Use new API for headers

* Update packages/bsky/src/data-plane/server/indexing/plugins/post.ts

Co-authored-by: devin ivy <devinivy@gmail.com>

* revert rm

* rm timeout

* cursor test

* Changeset

* Remove pds specific bump

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>
2024-08-21 12:33:05 -05:00
github-actions[bot] 5e2f2617ab Version packages (#2726)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-20 11:41:33 -04:00
devin ivy f9a2f3ed17 Fix calls from pds containing content-type but no body (#2725)
* pds: fix calls from pds containing content-type but no body

* build
2024-08-18 19:32:35 -04:00
github-actions[bot] 40c145fb16 Version packages (#2712)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-18 16:34:50 -04:00
Daniel Holmgren 50c0ec176c Service auth method binding (lxm) (#2663)
* add scopes to service auth impl

* add error to getServiceAuth

* send scoped tokens from pds

* clean up privileged access scopes & allow simple service auth tokens for app passwords

* integration into ozone

* fix up bsky tests

* cleanup xrpc-server tests

* fix up tests & types

* one more test

* fix read after write tests

* fix mod auth test

* convert scopes to be a single method name

* add scope check callback for auth verifier

* pds changes only

* fix feed generation tests

* use scope for ozone service profile

* dont verify scopes on pds yet

* tidy

* tidy imports

* changeset

* add tests

* tidy

* another changeset

* scope -> lxm

* tidy

* clean up scope references

* update nonce size

* pr feedback

* trim trailing slash

* nonce -> jti

* fix xrpc-server test

* allow service auth on uploadBlob

* fix build error

* changeset

* build, tidy

* xrpc-server: update lxm claim check error

* appview: temporarily permit labeler service calls to omit lxm claim

* xrpc-server: fix test

* changeset

* fix merged tests

---------

Co-authored-by: Devin Ivy <devinivy@gmail.com>
2024-08-18 15:46:07 -04:00
Matthieu Sieben acbacbbd56 Ensure presence of DPoP related response headers (#2711)
* fix(pds): ensure presence of DPoP related response headers

* Expose the request context for AuthVerifier and StreamAuthVerifier as distinct types

* Properly type ReqCtx for stream auth
2024-08-13 16:41:36 +02:00
github-actions[bot] cf988a5b4c Version packages (#2709)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-12 22:55:19 -04:00
github-actions[bot] 3940733bf0 Version packages (#2706)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-12 16:11:19 -04:00
Matthieu Sieben b934b396b1 Client SDK rework (#2483)
* feat(api): support creation of oauth based AtpAgents

* oauth: misc fixes for confidential clients

* fix(xprc): remove ReadableStream.from polyfill

* OAuth docs tweaks (#2679)

* OAuth: clarification about client_name being shown

* OAuth: re-write handle resolution privacy concern

* avoid relying on ReadableStream.from in xrpc-server tests

* feat(oauth-types): expose "ALLOW_UNSECURE_ORIGINS" constant

* feat(handle-resolver): expose "AtprotoIdentityDidMethods" type

* fix(oauth-client): ensure that the oauth metadata document contains client_id_metadata_document_supported

* fix(oauth-types): prevent unknown query string in loopback client id

* fix(identity-resolver): check that handle is in did doc's "alsoKnownAs"

* feat(oauth-client:oauth-resolver): allow logging in using either the PDS URL or Entryway URL

* fix(oauth-client): return better error in case of invalid "oauth-protected-resource" status code

* refactor(did): group atproto specific checks in own

* feat(api): relax typing of "appLabelers" and "labelers" AtpClient properties

* allow any did as labeller (for tests mainly)

* fix(api): allow to override "atproto-proxy" on a per-request basis

* remove release candidate versions from changelog

* update changeset for api and xrpc packages

* Add missing changeset

* revert RC versions

* Proper wording in OAUTH.md api example

* remove "pre" changeset file

* xrpc: restore original behavior of setHEader and unsetHeader

* docs: add comment for XrpcClient 's constructor arg

* feat(api): expose "schemas" publicly

* feat(api): allow customizing the whatwg fetch function of the AtpAgent

* docs(api): improve migration docs

* docs: change reference to BskyAgent to AtpAgent

* docs: mention the breaking change regarding setSessionPersistHandler

* fix(api): better split AtpClient concerns

* fix(xrpc): remove unused import

* refactor(api): simplify class hierarchu by removeing AtpClient

* fix(api): mock proper method for facets detection

* restore ability to restore session asynchronously

* feat(api): allow instantiating Agent with same argument as super class

* docs(api): properly extend Agent class

* style(xrpc): var name

* docs(api): remove "async" to header getter

---------

Co-authored-by: Devin Ivy <devinivy@gmail.com>
Co-authored-by: bnewbold <bnewbold@robocracy.org>
Co-authored-by: Hailey <me@haileyok.com>
2024-08-12 19:57:21 +02:00
Daniel Holmgren 8092715ea0 Allow aud of pds or entryway for service auth tokens on pds (#2694)
allow aud of pds or entryway for service auth tokens on pds
2024-08-06 19:02:00 -05:00
github-actions[bot] 66484a1041 Version packages (#2689)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-05 18:10:44 -05:00
Daniel Holmgren 269cbc87c5 PDS - inspect bearer tokens (#2688)
* inspect bearer tokens to determine if theyre access tokens or service auth tokens

* changeset
2024-08-05 18:05:47 -05:00
github-actions[bot] b0a5fa3d70 Version packages (#2685)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-05 15:21:57 -05:00
Daniel Holmgren dc471da267 Service auth method binding - PDS (#2668)
* pds changes only

* use scope for ozone service profile

* dont verify scopes on pds yet

* tidy

* tidy imports

* changeset

* add tests

* another changeset

* scope -> lxm

* tidy

* update nonce size

* pr feedback

* trim trailing slash

* nonce -> jti

* fix xrpc-server test

* allow service auth on uploadBlob
2024-08-05 15:09:50 -05:00
bnewbold a95a902bba minor typos in descriptions and comments (#2681)
* lex: typos in descriptions

* more minor typos

* codegen lexicon typos

* more comment typos
2024-08-05 09:49:25 -07:00
github-actions[bot] c007e930ed Version packages (#2677)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-02 17:17:16 -05:00
Eric Bailey 951a3df15a Remove detach (#2676)
* Remove detach record

* Changeset
2024-08-02 15:19:29 -05:00
Hailey 1419114394 Run test env with updateSnapshot (#2674)
* nit

* pds too
2024-08-01 13:41:59 -07:00
github-actions[bot] c5c5203a6e Version packages (#2671)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-31 16:51:04 -05:00
Eric Bailey ff803fd2bf [Detach] Lexicons (#2664)
* Define new detach record

* Codegen

* Add limit of 1k

* Codegen

* add protos

* tweak schema

* Add changeset

---------

Co-authored-by: dholms <dtholmgren@gmail.com>
2024-07-31 16:45:20 -05:00