Friday, October 22, 2021

Visual Studio Code + TypeScript + React - how to start in 10 minutes (fall 2021 tutorial)

Just a short tutorial on how to start your adventure with TypeScript + React with Visual Studio Code. Without further ado:
Install node.js. Install Visual Studio Code.
Create an empty folder. Go to it and run VSC from there from the OS shell
code .
When in VSC, open the terminal and work from the terminal rather than the OS shell
npm init -y
npm install webpack webpack-cli
npm install typescript ts-loader
npm install react react-dom
npm install @types/react @types/react-dom
This will create package.json and install your dependencies.
At the root of your app create two files, tsconfig.json

{
    "compilerOptions": {
        "allowJs": false,
        "baseUrl": "./",
        "jsx": "react",
        "declaration": false,
        "esModuleInterop": true,
        "lib": ["ES6", "DOM"],
        "module": "commonjs",
        "moduleResolution": "node",
        "noImplicitAny": true,
        "outDir": "./dist/",
        "paths": {
        "@/*": ["src/*"]
        },
        "sourceMap": true,
        "target": "ES6"
    }
}
and webpack.config.js
    const path = require('path');
    const TerserPlugin = require("terser-webpack-plugin");

    module.exports = {
    //mode: 'production',
    mode: 'development',
    target: ['web','es6'],
    entry: {
        'index': './src/index.tsx'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    devtool: 'source-map',
    module: {
        rules: [{
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
        }, ],
    },
    externals: {
        // Use external version of React
        //'react': 'React',
        //'react-dom': 'ReactDOM'
    },
    resolve: {
        // https://getfrontend.tips/shorten-import-paths-in-webpack/
        alias: {
        // Assume that the `src` folder is located at the root folder
        '@': path.join(__dirname, 'src'),
        },
        extensions: ['.tsx', '.ts', '.js'],
    },
    optimization: {
        minimize: false, // true/false
        minimizer: [
        new TerserPlugin({
            extractComments: false,
            terserOptions: {
            format: {
                comments: false,
            },
            },
        })
        ],
    },
    };
There are few options in the webpack's configuration file that can be further changed, pay close attention to mode and minimize.
Now create two empty folders, src and dist, the first is where your source code goes, the second is where webpack will store its output.
Now create two TypeScript files. First is your entry point, index.tsx
import React from 'react';
import ReactDOM from 'react-dom';

import App from '@/app';

/**
 * Entry point
 */
class Program {
    
    Main() {

        var app = (
                <App />
        );

        ReactDOM.render(app, document.getElementById('root'));
    }
}

new Program().Main();
Second is your app's component, app.tsx
import React from 'react';

const App = () => {

  return <>  
    Hello world from React & Typescript
  </>
};

export default App;
The general rule here is that
  • if a module is a pure TypeScript, your file can have *.ts extension
  • if a module should contain JSX code, your file should have *.tsx extension
Modules are imported/exported between both without any restrictions.
Now add the last file, app.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script defer src="/dist/index.js"></script>
    <style>
    * , *:before, *:after { 
         box-sizing:border-box; 
    }
    html {
        background-color: rgb(234, 238, 243);
    }

    </style>
</head>
<body>    
    <div id="root"></div>
</body>
</html>
What you should have right now is
Now just invoke
webpack
(this assumes webpack is available globally, if not, npm-install both webpack and webpack-cli as global modules:
npm install -g webpack webpack-cli
) and the application will be built and the output, dist/index.js will be created. This is a single file bundle and it can be further optimized by switching mode to production in the config, as well as turning on minimization.
To test the app, install and run a simple HTTP server, e.g. live-server
npm install -g live-server
live-server
and add a launch configuration in VSC
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "pwa-chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:8080/app.html",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
Congratulations, you can start your app with F5 from within VSC, put breakpoints in your TypeScript and debug your code and continue work on your first great app. Happy coding!

Wednesday, October 20, 2021

IndexedDB can possibly fail on FireFox

A couple of minutes and a good coffee spent on an issue. The idb, a wrapper over IndexedDB was failing silently. An isolated demo was luckily showing something, it was A mutation operation was attempted on a database that did not allow mutations in the console.
What Google tells about this issue is that it's related to working in private mode. Which was definitely not my case. However, another very old discussion mentions a setting that turns off history. And yes, the setting is there, in Firefox. Go to Options, then Security and Privacy and then there's a switch that either turns on or turns off history tracking. For some reason it was turned off in my FF.
And yes, turning it on brings IndexedDB back to life.

Friday, October 1, 2021

tiny-totp.js, a tiny TOTP javascript implementation

Just published the tiny-totp.js code on Github. It's super tiny Javascript RFC6328 implementation. If you wonder what's that, it's the Timed One-Time Passwords specs that is widely used to implement two-factor authentication over web.

What this tiny library does is it computes one time passwords, given the master password. It's basically the same computation Google Authenticator or Microsoft Authenticator do, given the master password. It means that you can either implement your own client or even your own server side authentication that would be compatible with existing authenticators (including the two).

What's interesting here is that the implementation is really, really tiny and has 0 dependencies. Usually, people implement TOTP using two dependencies:

  • to handle the base32 encoding (master keys are usually provided in base32 encoding
  • to handle the hmacsha1 to calculate actual codes
This code avoids both dependencies - there's a tiny function to handle base32 and platform specific crypto provider is used to handle hmacsha1 (the node's crypto module when run against node or window.subtlecrypto when run in a browser).

Thursday, August 26, 2021

TypeScript puzzle No.1 (easy)

There's a callable type definition
type Callable = {
    description: string;
    (a: string): string;
}
It's pretty obvious that if given an instance of the type, one can just call it:
function CallableClient( c: Callable, s: string ): string {
    return c(s);
}
The question is however, how to create instance of the type so that it's both callable and has the string property and the creation is strongly typed (doesn't involve any type). Formally, your task is to implement the factory method that takes a function, a description and composes them:
function F2Factory( f: (a:string) => string, description: string ): Callable {
    return ...; // ??
}

This is actually pretty interesting so here's goes the story. There are two possible approaches I am aware of.

First approach involves a const that is further expanded with an attribute:

const c : Callable = (s: string) => s;
c.description = "foo";

// c is correctly of Callable type and can be called
Note that it only work when the variable is declared as const, doesn't work with var/let. This should be enough to provide the implementation of the F2Factory

Second approach involves Object.assign which is typed as Object.assign<T, U>( t: T, u: U ) : T & U. It's great as it looks like it could just combine two objects and return a new object that act as both. Of course then, this works

function F2Factory( f: (a:string) => string, description: string ): Callable {
  return Object.assign(f, { description });
}

const c = F2Factory( (s: string) => s, 'foo' );

console.log( c('bar') );
console.log( c.description );
Note, however, that there's a caveat here. The Object.assign basically duplicates attributes of the source to the target. However, a callable object (a function) doesn't have any properties that can be duplicated to another object so that the other object would become callable too. This doesn't work then:
function F2Factory( f: (a:string) => string, description: string ): Callable {
  return Object.assign({ description }, f);
}

const c = F2Factory( (s: string) => s, 'foo' );

console.log( c('bar') );
console.log( c.description );
This could be surprising for someone, it doesn't work even though it types correctly!

The reason here is that for the typescript compiler, using Object.assign to combine a function and an object in any order produces the same output type. However, for the Javascript engine that actually runs the code, adding description to a function works but trying to duplicate callability to a simple object { description } by no means makes this object callable.

This discrepancy between the Typescript compiler and the Javascript runtime would be even more clear if we consider a type that combines two callable members

type Callable2 = {
    (a: number): number;
    (a: string): string;
}
This time the Object.assign will never yield expected output - it can't combine two functions to create yet another function that correctly handles input argument type.

Friday, July 30, 2021

Javascript Puzzle No.2

Given the code
let working = {
  foo: function() {
    console.log("working");
  }
};

let notworking = {
  foo() {
    console.log("notworking");
  }
}

new working.foo();
new notworking.foo();
and its output
Uncaught TypeError: notworking.foo is not a constructor
please explain why this particular function cannot be used as a constructor function.

Hint: based on this SO question where the explanation is provided.

Friday, June 18, 2021

PrincipalContext::ValidateCredentials doesn't always work like you think it should (false positives!)

We've recently stumbled upon a nasty issue with PrincipalContext::ValidateCredentials, the one you could use to authenticate users against given Active Directory LDAP server. The library method is often referred to and there are tons of references to use it just like
var principalContext = new PrincipalContext(ContextType.Domain, domain);
principalContext.ValidateCredentials(username, password);
And this is where the problem is.
One of our Clients pointed us to an edge case where somehow expired passwords were accepted. We confirmed that passwords are expired for sure (some passwords were expired for over a year!). They had another service that uses the LDAP authentication and the two services were uneven: the other service was rejecting user credentials, our was happily accepting users. The other service doesn't use .NET's built in method of course.
What we have found is a complaint of someone who posted in 2016 their own issue where for some reason expired passwords were accepted over LDAPS. The post says
I am using PrincipalContext.ValidateCredentials(string, string) to validate user credentials. I am encountering behaviour that I am unable to explain and have, so far, been unable to clarify through usual searches and forum visits. Here is the behaviour, the credentials can be assumed to be correct on each call. The non-SSL behaviour is what I would expect. The SSL behaviour is not as expected in my understanding of the method.
Not using SSL/TLS

User is enabled and NOT expired - method returns TRUE
User is disabled and NOT expired - method returns FALSE
User is enabled and EXPIRED - method returns FALSE
User is disabled and EXPIRED - Method returns FALSE
Using SSL/TLS

User is enabled and NOT expired - method returns TRUE
User is disabled and NOT expired - method returns FALSE
User is enabled and EXPIRED - method returns TRUE
User is disabled and EXPIRED - Method returns FALSE
To summarise - when both machines in the negotiation (app server and domain controller) have the required certs installed, they will use SSL (port 636). In these circumstances the method returns TRUE for Expired accounts but when not using SSL (port 389) (for example the app server does not have the cert installed) the method is returning FALSE for Expired accounts. I would expect the return value to be the same for expired accounts in both scenarios, but it's possible I'm not considering something.
This looked like the case, yes, we've turned on SSL over LDAP for this particular installation. Yes, we also see that expired passwords are accepted.
Unfortunately, the post author is asked to create an entry on Connect and as we already know, Connect has been closed. No solution then.
But what ValidateCredentials has to do with LDAPS? Well, we've found another clue. Someone at StackOverflow describes how ValidateCredentials works internally:
Here's how ValidateCredentials(string, string) works: First, it tries to authenticate with the Negotiate, Signing, and Sealing context options. If this fails, it tries again with SimpleBind and SecureSocketLayer.
Ha! There goes the neighbourhood. When ValidateCredentials fails on Kerberos (it fails because the password is expired), it falls back to SimpleBind (basic auth) over SSL (which succeeds!).
Turning LDAPS off is not an option here but surely this is an option: instead of
var principalContext = new PrincipalContext(ContextType.Domain, domain);
principalContext.ValidateCredentials(username, password);
just make sure you call it
var principalContext = new PrincipalContext(ContextType.Domain, domain, ContextOptions.Negotiate | ContextOptions.Sealing | ContextOptions.Signing);
principalContext.ValidateCredentials(username, password);
This makes sure there's no SimpleBind+SSL fallback as it forces Kerberos only. And guess what? It just works correctly. Expired passwords are no longer accepted.

Tuesday, April 27, 2021

Microsoft Teams loads but renders a blank (white) window

Today I've spent like an hour trying to fix a sudden issue - Teams loads, the loading window shows correctly but then, instead of a normal main window, all I got was a blank white window.
It never happened before and of course I've tried to search for similar problems people had and possibly found solutions. I've tried all of this:
  • closing/reopening Teams
  • shutting down the machine
  • reinstalling Teams
  • removing both ..\App Data\Local\Microsoft\Teams and ..\App Data\Roaming\Microsoft\Teams
  • creating a custom link that points to Teams.exe instead of Update.exe --processStart "Teams.exe" where the default link points to
  • forcing a custom power scheme for the application so that for my dual GPU machine, the correct GPU is used
None of these worked.
What I suspected is that the Teams problem is somehow related to account switching. The default Windows client doesn't support multiple accounts, having two of them (for my two organizations) means I have to sign out and relogin to the other account every time I want to use it (alternatively I could switch to another user profile). This always worked. But today - the white window was clearly related to signing out of a working session and trying to sign into the other account.
What people recommend is to remove all Windows 10 linked accounts (work/school accounts in the Windows Credentials Manager) and this didn't help either. Somehow I noticed, however, that even removing a user profile (..\App Data\Roaming\Microsoft\Teams) caused my Teams to show the login prompt but skipping the password prompt. This leads to an interesting question: where does Teams cache user credentials? Interestingly enough, people also noticed this (Google "Teams Not Asking for Password").
The end of this story is: while I was not able to find a way to clear the cache locally, I just changed my domain's account password in the remote organization's registry and then cleared the Teams profile. This once again triggered the login prompt but this time it also asked for a password. And Teams loaded correctly.
Hope this helps someone in trouble.