It is with great pleasure that I announce the release of EndBASIC 0.9.0 😎️.
The major feature in this new release is the ability to launch publicly-shared files via a click of a URL without having to create an account first. Here, try running my jmmv/bounce.bas
or jmmv/paint.bas
demos in your browser, now!
A blog on operating systems, programming languages, testing, build systems, my own software projects and even personal productivity. Specifics include FreeBSD, Linux, Rust, Bazel and EndBASIC.
This may seem like a small feature not worthy of a new release, but it is a game-changer for two reasons. First, the backend has undergone massive changes to support this, which in turn have allowed me to fulfill my original vision for the cloud service. And, second, this lets passersby trivially play with anything you create. The barrier to entry is now much lower for new developers as well as observers.
This release includes other changes as well, primarily around fixing UTF8-related crashes in the command line and the editor. These are special and worth calling out because they are the first substantial code contributions from a user, @zenria. Thanks!
Finally, the website now includes a User’s manual. This manual is intended to provide an overview of the features in EndBASIC and how they all tie together, and I hope can help get an overview of what’s supported before even diving into the language. Note that this is not meant as a replacement for the built-in reference documentation, whose primary source is still within the interpreter.
Here are the links you need to get started:
Please let me know if you encounter any issues.
Enjoy and keep reading if you want to know more about what has happened behind the scenes, as this release has taken about 2 months of early morning and weekend work.
Refactoring authentication
Modifying the EndBASIC cloud service to support unauthenticated access to publicly shared files has been… very difficult.
To set some context, the cloud service grew as an experiment of mine to learn more about Azure. As such, I ended up using Azure Active Directory (AAD) for authentication and Azure Functions as the runtime for the service. This combination works nicely if you choose to enable authentication in the Azure Functions configuration: the runtime will handle integration with AAD for you and only allow authenticated calls to go through.
Unfortunately, setting up authentication on an Azure Functions instance is an all-or-nothing option. Either you protect the whole service with authentication or you don’t. It’s not possible to serve some authenticated and some unauthenticated endpoints at once from the same deployment. Well, it is possible if you roll your own bearer token validation, but that requires a significant amount of code. I suppose that the Azure SDKs for officially-supported languages such as C# let you do this with ease, but the Rust SDK is… well… lacking.
I had various options here:
I could have come up with a humongous hack by deployed two separate Azure Functions instances: one with authentication as before, and one without authentication to support this new use case. This would have required adding a simple knob in the code to configure the latter deployment to only allow access to public content. But… this is how operational complexity is made when launch schedules dictate cutting corners.
I could have implemented my own bearer token validation. In fact, I did research this quite a bit and had it almost working, but I stopped before implementing signature validation because this required fetching and caching service-specific public keys with certain refresh periods… and I was lazy to code it all.
I could have dropped AAD altogether and implemented my own account management and authentication mechanism.
And option 3 is, of course, the one I chose.
Now, before you call me reckless, there is a good reason I went for option 3 and implemented my own authentication flow. The reason was to avoid OAuth—there are good reasons to do so!—which had prevented me from implementing the signup flow I wanted to have from the very beginning.
Account creation
EndBASIC 0.9 implements the original vision I had for account creation, which is made possible by the cloud service not using OAuth any longer.
Before this release, you had to visit the EndBASIC web site to start an account creation flow in AAD, go through that bland process, and then come back to the interpreter to log in. What I really wanted from the very beginning was to have a SIGNUP
command within the interpreter that acted like adduser
on FreeBSD, and this is now what we have:
Ready
SIGNUP
Let's gather some information to create your cloud account.
You can abort this process at any time by hitting Ctrl+C and you will
be given a chance to review your inputs before creating the account.
Username: demo
Password: *********
Retype password: *********
[...]
Continue (y/N)? y
Your account has been created and is pending activation.
Check your email now and look for a message from the EndBASIC Service.
Follow the instructions in it to activate your account. Make sure to
check your spam folder.
The client aspects of this were trivial to implement, but the service side wasn’t. To make this work, I had to implement the account signup flow with email validation, which means I had to generate and track account activation codes, deal with secure password hashing, and integrate with SendGrid to send service-related emails. Not inherently difficult code to be honest, but getting the tests updated was painful.
Lost accounts
Everything sounds great but there are some bad news, unfortunately. Rolling my own account management means that I had to migrate user accounts from AAD to my own accounts system. Which was doable… except for passwords. For obvious reasons, AAD doesn’t store raw passwords, which means it’s just impossible to fetch them and put them in a different database. But I could have migrated the accounts by hand and emailed all account owners to reset their password, right?
Well… you see, while I had configured the AAD signup flow to perform email validation and I had code in the service to save the email claim in the OAuth token in the shadow accounts table… I had misconfigured the OAuth login flow by forgetting to enable email claim propagation. This means that my service never saw the email addresses and they were never stored in AAD. Simply put, I have no way of contacting previous account owners.
Considering that there were only a few accounts and that they are mostly inactive, wiping everything didn’t seem like a big deal and is what I did. Apologies for the hassle.
Fear not, though: I have a backup of all previous data so if you want me to recover anything for you, just let me know.
What’s next?
I had bigger plans for this release. On top of public file sharing, I planned to finally add support for user-defined functions, GOTO
and GOSUB
, file system directories, and maybe even automatic numeric type promotion. But because the changes to the cloud service turned out to be so intrusive and required a database wipeout, I preferred to cut scope and get this launched ASAP to get the new code exercised and avoid losing even more data.
The aforementioned features should come in 0.10 though, so stay tuned for a much better language! A great way to do this is to launch the interpreter, create an account with SIGNUP
, and opt into receiving promotional (release announcements) emails! Or you can subscribe to this blog with the buttons below.
Thanks for reading, and have fun 😁️.