Obsession with abstraction

I've always been obsessed with abstraction because I like my code to look clean and legible for the next person (Even though I know there will not be a next person). I love the look of "clean" code, even though it's not necessarily more performant (more abstraction = slower the program is) than code that is harder to read. 

I enjoy the "appeal" more than the performance, even though this is not something I would do in production; cause like why not? Oh, I also picked up The Pragmatic Programmer.

Dating apps are bullshit

I was scrolling through hacker news and found this Young women fall out of love with dating apps | Hacker News (ycombinator.com)

I personally never used a dating app before, only because like I knew they were bullshit from the beginning. These dating apps were created by publicly tradeable companies, meaning they have to continuously turn a profit year by year and increase those margins by X in order to please the shareholders. The idea of the app is dating, yes. Still, the end goal for them is to squeeze as much as possible out of their consumer. Their objective isn't to get you a date it is to make money off you.


Prediction:

When these dating apps (which they already are) start to fall apart, I think these companies will create fake accounts to deceive their shareholders and increase their stock prices. This will probably end in a class action lawsuit against the SAID company.

Affluenza

I heard about the Ethan Couch story in 2016, and a psychologist, I think, diagnosed him with "Affluenza," I can only say that so many people on social media have "Affluenza." I guess another word for them is "clout chasers." It's not exactly the same thing, but clout chasers can have and/or develop "Affluenza," However, I think it's safe to say these words can be synonyms. 


One project I am proud of even though I never finished it.

source: chomnr/licensing.net: in heavy development, don't use. (github.com)

About nine months ago, I wrote a pretty simple licensing system in C#, and I never got around to finishing it, but man, it's probably the only project where I like how the code looks. Personally, I think it's just really clean and straightforward. Even after nine months, I still understand why I put that there and I don't get lost anywhere.

The cool part of the project is that I wrote an "authority" system for my CRUD functions that manage the license. Basically, I can apply a set of rules to each CRUD function, and the function must follow these "rules" in order to successfully execute.  


public async Task LicenseActivateEvent(string key)
{
    LicenseLookUp lookUp = new LicenseLookUp(null, null, key);
    LICENSE_STATUS[] statusToLookFor = new LICENSE_STATUS[] { LICENSE_STATUS.SUSPENDED, LICENSE_STATUS.UNCLAIMED };
    IAuthorityRule[] rules = { new RequireStatusRole(statusToLookFor), new RequireOwnershipRule(), new RequireDurationRule() };
    LicenseResult result = await Authority
        .SetErrorMessage("Activation has failed either because the license does not exist, is deactivated, is currently activated, has no owner, or the duration is set to 0.")
        .AddRules(rules)
        .RunOn(lookUp);

    if (result.AuthorityState == AUTHORITY_STATE.APPROVED)
    {
        var status = result.License.Status;
        if (status == LICENSE_STATUS.SUSPENDED)
        {
            result.License.PurchaseDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        }
        result.License.Status = LICENSE_STATUS.ACTIVATED;
    }
    return result;
}   

To activate a license, you need to ensure that it has a Status, an owner, and a duration. If the AuthorityState == APPROVED, then it will grab the current status of the license. If it's suspended, it will reset the purchase date to ensure the user gets their entire duration.

I built this system to avoid using if statements everywhere. I thought this was a pretty clever way of circumventing that, and I'm proud of myself.

I might continue this project in the future, but I am not sure why I abandoned it? I think it was to build my security monitor, as you can see chomnr/live-security-monitor: A real-time brute force logger, this system meticulously logs and publicly exposes unauthorized access attempts. (github.com).

Project Breakdown: Ark (Repost)

I originally posted this on substack, but I decided to switch to this platform.

What is Ark?

notpointless/ark is a full-stack monolithic application that heavily emphasizes its (IAM) solution. Initially, it was supposed to be closed source because it was going to be used for a side project, but I decided to release it to the public for personal reasons.

Technical Breakdown

My thought process and the reasoning behind my decisions during the construction of this project. I will also detail the pros and cons of each aspect of the project, what I could have done better, and alternative approaches.

Task System

The idea behind this is that instead of re-instating the desired database instance to create or update a specific field within a database, we can queue the desired task without calling the database every single time through the function. This ends up streamlining the function.

so instead of this:

pub fn create_permission(database: PostgresDatabase, permission: Permission) -> Result<T> {/* perform operation here */}

we have this:

pub fn create_permission(permission: Permission) -> TaskResult<T> {/*queue message here*/}

So, instead of instantiating it through a CRUD function, the Task System provides the instantiation when it receives the task.

Due to the way the Task System was built, you need to specify what type of task you would like to use when composing a message so it knows what handler to use.

In order to successfully compose and send a task to the Task System to handle; A few things need to be done, you need three things: TaskType, TaskHandler<D>, and Task<D, R, P>.

  • TaskType, will help identify what handler to use.

  • TaskHandler<D> will assign a specific “action” to Task<D, R, P>

    • D: Database

  • Task<D, R, P> will perform the actual task.

    • D: Database

    • R: TaskRequest

    • P: The Type

After all those things are satisified you need to ensure the TaskType is registered with the corresponding listener. You can look at how to register a listener here.

Now you can create a function that sends a request to the task channel.

pub fn create_role(role: Role) -> TaskResult<TaskStatus> {
      let task_request = Self::create_role_request(role);
      TaskManager::process_task(task_request)
}
fn create_role_request(role: Role) -> TaskRequest {
      TaskRequest::compose_request(RoleCreateTask::from(role), TaskType::Role, "role_create")
}

TaskRequest will compose a request that the Task System can interpret then we can call TaskManager::process_task(task_request); this will return a Completed or Failed status. If you’re expecting a custom type to be returned then you can instead call TaskManager::process_task_with_result::<T>(request); instead of process_task.

Channeling

When the Task System recieves a request it first gets sent to the INBOUND channel then it sends its results to the OUTBOUND channel. This creates a bidirectional channels and this is how we can retrieve the results.

Advantages/Disadvantages

The amount of complexity that gets added is astonishing and it’s not a good thing. Also the inability to actually sends tasks within another task creates another issue on its own.

Advantages

  • Function Simplicity

  • Granular Control

Disadvantages

  • Complexity

  • No Nested Tasks

Alternative Approaches (Possible)

  • Use a messaging broker instead of using channels.

  • Use Redis pub/sub instead of channels.

  • Add the tasks to a hashmap first, then have the task system pull directly from the hashmap. The only con is I will need multiple hashmaps for each type. And additional complexity. Not entirely sure if this would solve being unable to call nested tasks.

  • Using serialization to prevent the overuse of generic types.

Cache System

The same as the Task System, but instead of using PostgreSQL, Redis is used. I separated them because I can’t call a task within a task, meaning I cannot call a channel within a channel unless it comes from a different instance. So, instead of calling a task within a task, it is being called from a cache within a task because cache uses a separate instance of channel than the task.

IAM (Identity And Access Management)

The IAM I built for this project is relatively rudimentary. It doesn’t contain effects, resources, or policies. It’s based on the user’s role and permission. So, I guess you could still call it an IAM?

Permission

I integrated permissions entirely because I was looking for something flexible. The permissions are stored inside PostgreSQL but are cached quite differently. You are not supposed to use the Cache System to cache these because they’re not stored inside Redis but locally inside a HashMap. The reason I did was for quantity. You might only expect a couple hundred permissions, but you will expect a couple hundred thousand users.

Caching:

Like I mentioned above the caching mechanism is different. But it is pretty straight forward. If someone wants to use a local cache then can they skip over the Cache System and use LocalizedCache<T> T signifies what type you want to cache. For example: Permission. So you would use impl LocalizedCache<Permission> for PermissionCache then implement the required types.

Whenever you call fn add(item: T), it will add three keys: permission_id, permission_name, and permission_key. I chose to add three other keys because all these fields are unique inside the database; therefore, I want permissions to be retrievable by their ID, name, or key.

Also, the values for all the keys all use an Arc<T> T being the same T for LocalizedCache<T>; therefore, I could implement an fn update() and update a single field, and it will automatically update the other two because they’re a shared state. I just wanted to say this because I’m well aware.

Schema:

  • permission_id: the uuid of the permission (randomly generated using the uuid crate)

  • permission_name: the name of the permission. Example: “Ban User”

  • permission_key: the key of the permission. Example: “admin.ban.user“

The permission_name and permission_key can be in any format. This is just the ideal example of what it should look like.

Relation

The roles and permissions are connected, and to ensure consistency among them, the roles type takes a field called pub role_permissions: Vec<String>. This contains only the id of the permission entirely because that is the only immutable field among a permission.

Role

I think of roles as just a bunch of permissions grouped into one place, so I added that. I wanted predefined permissions for specific roles. So I wouldn’t have to assign, for example, the ban permission for every user; instead, I could assign them the role and adjust the permissions accordingly. It ensures consistently among roles.

Schema:

  • role_id: the uuid of the permission (randomly generated using the uuid crate)

  • role_name: the name of the permission. Example: “Moderator”

The role_name and role_id can be in any format. This is just the ideal example of what it should look like.

User

If you look through the schema.sql and the iam_users table, you will notice that they do not contain passwords because users can only log in through an OAuth2 provider such as Google and Discord.

Resetting Passwords, Emails etc;

If you look at the iam_users table again, you will notice a security_token and security_stamp field. ASP.NET CORE heavily inspired this. I wasn’t entirely sure how they did it, but I know whenever the security_stamp gets changed, the security_token gets invalidated. Right now I’m still trying to figure out how I would go about verifying it, but I have an idea, but before I tell you that I should break down how it exactly works at this current moment.

security_stamp

The model for the security_stamp doesn’t have a model but instead just takes String type.

In order to generate a valid security_stamp the function fn generate_security_stamp(security_stamp: String, action: &str);

This function simply gets the current time in milliseconds then hashes it with Sha256 then turns it into a hexadecimal string.

security_token

The model for the security_token is

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
// security token for the user...
pub struct SecurityToken {
    pub token: String,
    pub expiry: u128,
    pub action: String,
}
  1. First we call SecurityToken::new(security_stamp: String, action: &str);

  2. Then we serialize with the model with serde_json then we convert the serialization into a hexadecimal.

  3. After we have the hexadecimal of the given model then we can store that into the database.

In order to grab the value we need to call decode_then_deserialize(security_token: Option<String>

This function decodes the hexadecimal string, then deserializes it and casts the generic type SecurityToken. If the token is empty, it returns None.

Shorthand

The function you would use to generate the actual token with the specified action is

UserSecurity::create(action); so if you would like to create an email reset you would do UserSecurity::create(“email_reset”); then in your route /auth/email_reset you would check if the SecurityToken exists and check if action is set to “email_reset” if not, deny access.

This is simple technical breakdown of the current status of my project.