Fork me on GitHub
Epidiah Ravachol15:03:28

I just wanted to say that I really appreciate this channel, even if I tend to use it as a place to type out and discard questions I end up answering myself in the asking.

gratitude 6

I have really enjoyed and benefitted from your questions and the answers. 🙂


And I’m also grateful for this channel!

🎅 2

@foo: I just started working on a modified auth plugin to support invite codes. I'd like the codes to have an associated used count, and become invalid when it's reached. It seems like I need a tx-fn to do this right; until now I've gotten by with just the biff-provided constraints (or punted on safety, since i'm just building exploratory apps that may never see the light of day). Writing the tx-fn is straightforward, as is adding it through start-system. What I'm wondering, though, is if perhaps plugins should have an optional entry for providing tx-fns, so that using a plugin that depends on a tx-fn doesn't require changes to both places. Maybe this is already something that would be made possible by the changes you sketch out in thinking-about-system-composition?

Jacob O'Bryant18:03:13

> What I'm wondering, though, is if perhaps plugins should have an optional entry for providing tx-fns, so that using a plugin that depends on a tx-fn doesn't require changes to both places. This might be a reasonable thing to do. However in this case I think you can get by without writing a tx fn, and in general I try to avoid it. You can use the :db/add operation to atomically increment numbers, e.g. the auth plugin does this for signin codes. It doesn't try to prevent someone from getting past the limit by submitting a bunch of codes all at once (though it does use recaptcha protection). To prevent that, you can use one of submit-tx's fancier features:

(let [code "abc123"
      email ""
      submitted-tx (biff/submit-tx ctx
                     (fn [{:keys [biff/db]}]
                       (let [invite (biff/lookup db :invite/code code)]
                         (when (and (some? invite)
                                    (< (:invite/uses invite)
                                       (:invite/max-uses invite 10)))
                           [{:db/doc-type :invite
                             :xt/id (:xt/id invite)
                             :db/op :update
                             :invite/uses [:db/add 1]}
                            {:db/doc-type :user
                             :db.op/upsert {:user/email email}
                             :db/joined-at :db/now}]))))]
  (if (some? submitted-tx)
    (println "user added successfully")
    (println "invalid invite code")))
(I'm pretty sure that will work) If you pass a function to submit-tx instead of a transaction (i.e. sequence of document operations), then biff will call that function with an up-to-date db value in order to get the transaction. and, crucially, if the transaction fails due to a contention (e.g. the :update operation fails because some other transaction modified the same document before this one got indexed), then biff will get a fresh db and call the function again. So you can use this to check for more complex constraints than what biff's regular operations provide.

😲 2
gratitude-thank-you 2
Jacob O'Bryant18:03:51

Oh, also: if you pass an empty transaction to submit-tx, then it will return nil. Otherwise it will return the result of xtdb.api/submit-tx. So you can use that to check for success without needing to throw an exception (though throwing an exception would also work).

Jacob O'Bryant18:03:15

transaction functions are useful when the constraints depend on documents that aren't being written to in the current transaction. e.g. hence the :biff/ensure-unique transaction function, for checking if some random document has been created/updated with a key that you're writing to your own document.

Jacob O'Bryant18:03:48

I guess you could think of passing a function to biff/submit-tx as sort of a "transaction function lite"... not sure what to call it since "transaction function" is already taken

Jacob O'Bryant18:03:57

a "not transaction function"

Paul Hempel18:03:51

@U043HTJ9RC2 would you mind sharing your solution once its ready? I have a similar use case, where invite codes are the way to go.


@U02DR6Q7NL8 was planning on it; hence the desire to do it 'right'

🙌 2
👀 2
😎 2

For those interested: how would you feel about the single-opt-in flag being always-on? (or rather: removed, and the behavior being as though it's on) I'm adding passwords too, for my own use-case, and it complicates things if I don't create the user right away. There's a lesser complication with invite-codes that makes me want to follow that path as well (saving which invite they used). Obviously there are ways to keep that info around, but I'm not sure anyone would really want to go that route, at least when using passwords. On the other hand, I do like the idea of it being a strict superset of the existing functionality. For now, I'm thinking I'll make it work with single-opt-in assumed to be true, but leaving those code-paths in, and put what I have on my github once it's working. It's a little 'icky', since you could configure it in a broken way, but it keeps the options more open. We can go from there, depending on feedback and interest.

Paul Hempel16:03:06

@U043HTJ9RC2 For me, a single-opt-in strategy would be preferable. My ‘target audience’ is happy if they don’t have any hassle and reducing it to one mail would be nice. Long term I may want to change it, if bots find the page – so a flag would be nice as well.

Paul Hempel16:03:58

@U043HTJ9RC2 on another note: you intend to add a ‘use count’ for the invite codes. Is there a reason you would not add a field to the user with the code. And everytime a new code gets requested it overwrites the existing one. If a code is used the existing code, attached to the user, gets removed. This is how I feel other apps behave – if you request multiple codes, only the last works and only one time.


I'm not sure what you mean, exactly. I want to be able to give someone an invite code that they can pass on to several others, with not-yet-known email addresses


it's certainly becoming clear to me why this is an ideal candidate for the more extensive 'plugability' we have now; so many combinations of choices. At some point, you really want your code to only support the ones you care about, for maintainability.


I got sidetracked making my signin/signup pages prettier, but I'll finish testing and upload something soon; hopefully you can adapt it to your use-case

Jacob O'Bryant17:03:53

haha yeah, auth is a huge can of worms. I think having a variety of plugins with focused scope makes sense. for password flows I think it's typical to create the user right away and mark their email as unverified. then the application developer can decide whether to gate certain features behind verification.

👍 2
Jacob O'Bryant17:03:17

(I always use single opt-in myself; I just didn't necessarily want to make that the default since I'd prefer people to be making a conscious choice about that. but it's probably mostly just a concern for apps that plan on sending regular emails anyway)

Jacob O'Bryant17:03:00

the terminology (single/double opt in) in particular is a newsletter thing


makes sense; will upload something after lunch. any thoughts on 'packaging' these, since they require at least a couple small changes to home.clj ? I was kinda thinking I'd make a new template project and embed it, but that's not very amenable to including one in deps.edn so you can easily get new versions. Maybe most people would want to copy the file and modify it anyway, though?


maybe ideally, you'd be asked about what kind of auth-scheme you want when making a template, and get a corresponding set of plugin options and sample home.clj - happy to collaborate on making that happen, if you don't think it over-complicates things

Jacob O'Bryant18:03:28

I would provide the plugin as a regular library, and include instructions + demo code for any changes the developer needs to make to their own project. if you want you could even provide library code that includes some UI, kind of like how Firebase UI has a predefined signin form you can use. for the main biff project I want to keep it to a single template. but I would be happy to incorporate changes into the default auth plugin if it feels like they'd be useful for enough people. I'd also be happy to link to other auth plugins etc from the docs.

Jacob O'Bryant18:03:43

passwords certainly would be great to have in the default plugin


having a bit more trouble debugging than anticipated; will have to finish tomorrow