Skip to content

Instantly share code, notes, and snippets.

@ollie314
Last active May 29, 2022 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ollie314/6ad34c977ac70502491b6606b203b0cc to your computer and use it in GitHub Desktop.
Save ollie314/6ad34c977ac70502491b6606b203b0cc to your computer and use it in GitHub Desktop.
Sample DDD implementation
// This is a work in progress to support a workshop.
module Utils {
export namespace Duration {
export type DurationTime = number;
export type DurationUnit =
| "h"
| "hours"
| "m"
| "min"
| "mins"
| "minutes"
| "s"
| "sec"
| "secs"
| "seconds";
export type Duration<
Time extends DurationTime = DurationTime,
Unit extends DurationUnit = DurationUnit
> = {
time: Time;
unit: Unit;
};
export function normalizeDuration<
Time extends DurationTime = DurationTime,
Unit extends DurationUnit = DurationUnit
>(duration: Duration<Time, Unit>): Duration<Time, Unit> {
let op: number = 0;
switch (duration.unit) {
case "h":
case "hours":
op = 60 * 60;
break;
case "m":
case "minutes":
op = 60;
break;
case "s":
case "seconds":
op = 1;
break;
}
return {
time: (duration.time * op) as Time,
unit: "s" as Unit,
};
}
}
export function pipe<A, B>(...fns: Function[]): (a: A) => B {
return (a: A) => (fns.reduce((acc, f) => f(acc), a) as unknown) as B;
}
}
module Iam {
// ================================================ //
// Model //
// ================================================ //
export type Username = string;
export type Password = string;
export type Email = string;
export type Identity = Username | Email;
export type Principal = {
credential: Identity;
password: Password;
};
export type RemindUser = boolean;
export type Scope = string;
export type Scopes = Array<Scope>;
export type RoleId = string;
export type Role = {
id: RoleId;
name: string;
scopes: Scopes;
};
export type Roles = Array<Role>;
// ================================================ //
// Identification //
// ================================================ //
// meaningfull constants
export const INVALID_USERNAME = "1";
export const UNKNOWN_USER = "2";
export const REMIND_USER = true;
export const DO_NOT_REMIND_USER = false;
// return type for the identification process
// Here is a pretty simple demonstration, a monadic type sounds far better here.
// Here we are synchronous for the sake of simplicity.
export type IdentificationSuccess = {
username: Username;
};
export type IdentificationFailure = {
code: string;
message: string;
error?: Error;
};
export type IdentificationResult =
| IdentificationSuccess
| IdentificationFailure;
export function identify(username: string): IdentificationResult {
if (username === "") {
return {
code: INVALID_USERNAME,
message: "The username must be provided",
};
}
if (username === "root") {
return {
username: username as Username,
};
}
return {
code: UNKNOWN_USER,
message: "The user does not exists",
};
}
// ================================================ //
// Authentification //
// ================================================ //
// meaningfull constants
export const INVALID_PASSWORD = "3";
export const INVALID_CREDENTIALS = "4";
// return type for the authentication process
// Monadic type sounds far better here one more time and
// we are synchronous for the sake of simplicity.
export type AuthenticationSuccess = {
username: Username;
remindMe: boolean;
roles: Roles;
};
export type AuthenticationFailure = {
code: string;
message: string;
};
export type AuthenticationResult =
| AuthenticationSuccess
| AuthenticationFailure;
// identify function
export function authenticate(principal: Principal): AuthenticationResult;
export function authenticate(
principal: Principal,
remindMe: RemindUser
): AuthenticationResult;
export function authenticate(
username: Username,
password: Password
): AuthenticationResult;
export function authenticate(
username: Username,
password: Password,
remindMe: boolean
): AuthenticationResult;
export function authenticate(
a1: Principal | Username,
a2?: RemindUser | Password,
remindMe?: RemindUser
): AuthenticationResult {
let username: Username;
let password: Password;
let remindMe_ = DO_NOT_REMIND_USER;
if (typeof a1 === "string") {
if (!a2 || typeof a2 !== "string") {
return {
code: INVALID_PASSWORD,
message: "invalid password",
};
}
username = a1;
password = a2;
if (remindMe !== undefined) {
remindMe_ = remindMe;
}
} else {
username = (a1 as Principal).credential;
password = (a1 as Principal).password;
if (a2 !== undefined) {
remindMe_ = a2 as boolean;
}
}
// a simple faker implementation of the control
if (username === "root" && password === "demodemo") {
return {
username,
remindMe: remindMe_,
roles: [
{
id: "1",
name: "root",
scopes: ["resources:read", "resources:write"],
},
],
};
}
return {
code: INVALID_CREDENTIALS,
message: "invalid credentials",
};
}
// ================================================ //
// Authorization //
// ================================================ //
export type SessionDurationTime = Utils.Duration.DurationTime;
export type SessionDurationUnit = Utils.Duration.DurationUnit;
export type SessionDuration = Utils.Duration.Duration<
SessionDurationTime,
SessionDurationUnit
>;
export type SessionInfo = {
duration: SessionDuration;
};
export type AuthorizationSuccess = {
username: Username;
roles: Roles;
sessionInfo: SessionInfo;
};
export type AuthorizationFailure = {
code: string;
message: string;
error?: Error;
};
export type AuthorizationResult = AuthorizationSuccess | AuthorizationFailure;
export function authorize(
username: Username,
userRoles: Roles,
acceptedScopes: Scopes
): AuthorizationResult {
const acceptScope = (s: Scope) => acceptedScopes.includes(s);
const scopes: Scopes = userRoles
.flatMap(({ scopes }) => scopes)
.filter(acceptScope);
if (scopes.length > 0) {
return {
username,
roles: userRoles.map((r) => ({ ...r, scopes })),
sessionInfo: {
duration: Utils.Duration.normalizeDuration({ time: 1, unit: "h" }),
},
};
}
return {
code: "-1",
message: "not implemented",
};
}
// ================================================ //
// Workflows //
// ================================================ //
export type AuthenticationAccepted = {
username: Username;
remindMe: RemindUser;
roles: Roles;
sessionInfo: SessionInfo;
};
export type AuthenticationRejected =
| IdentificationFailure
| AuthenticationFailure
| AuthorizationFailure;
export type AuthenticationWorkflowResult =
| AuthenticationAccepted
| AuthenticationRejected;
export type AuthenticateType = typeof authenticate;
export type AuthenticationWorkflowDependencies = {
identify: (username: Username) => IdentificationResult;
authenticate: AuthenticateType;
authorize: (
username: Username,
userRoles: Roles,
acceptedScopes: Scopes
) => AuthorizationResult;
};
export type AuthenticationWorkflow = (
deps: AuthenticationWorkflowDependencies,
login: Username,
password: Password,
remindMe: RemindUser,
acceptedScopes: Scopes
) => AuthenticationWorkflowResult;
// get a partial application to generate the workflow
export const getAuthenticationWorkflow = (
deps: AuthenticationWorkflowDependencies,
acceptedScopes: Scopes
) => {
type AuthenticationVector = {
login: Username;
password: Password;
remindMe: RemindUser;
};
type IdentificationVector = {
login: Username;
password: Password;
remindMe: RemindUser;
};
type IdentifiedVector = AuthenticationVector & IdentificationSuccess;
type AuthentifiedVector = IdentifiedVector & AuthenticationSuccess;
type AuthorizedVector = AuthentifiedVector & AuthorizationSuccess;
const toAuthenticationVector = (
login: Username,
password: Password,
remindMe: RemindUser
): IdentificationVector => ({
login,
password,
remindMe,
});
const ident = (auth: AuthenticationVector) => {
const identified = deps.identify(auth.login);
if ((identified as IdentificationSuccess).username) {
return {
...auth,
...(identified as IdentificationSuccess),
};
} else {
return identified as IdentificationFailure;
}
};
const authe = (vect: IdentificationFailure | IdentifiedVector) => {
if ((vect as IdentificationFailure).code) {
return vect as IdentificationFailure;
}
const principal: Principal = {
credential: (vect as IdentifiedVector).username,
password: (vect as IdentifiedVector).password,
};
const authenticated = deps.authenticate(
principal,
(vect as IdentifiedVector).remindMe
);
if ((authenticated as AuthenticationSuccess).username) {
return {
...(vect as IdentifiedVector),
...(authenticated as AuthenticationSuccess),
};
} else {
return authenticated as AuthenticationFailure;
}
};
const autho = (vect: AuthenticationFailure | AuthentifiedVector) => {
if ((vect as AuthenticationFailure).code) {
return vect as AuthenticationFailure;
}
const authorized = deps.authorize(
(vect as AuthentifiedVector).username,
(vect as AuthentifiedVector).roles,
acceptedScopes
);
if ((authorized as AuthorizationSuccess).username) {
return {
...(vect as AuthentifiedVector),
...(authorized as AuthorizationSuccess),
};
} else {
return authorized as AuthorizationFailure;
}
};
const normalize = (vect: AuthorizationFailure | AuthorizedVector) => {
if ((vect as AuthenticationFailure).code) {
return vect as AuthenticationFailure;
}
return {
username: (vect as AuthorizedVector).username,
remindMe: (vect as AuthorizedVector).remindMe,
roles: (vect as AuthorizedVector).roles,
sessionInfo: (vect as AuthorizedVector).sessionInfo,
} as AuthenticationWorkflowResult;
};
return (
login: Username,
password: Password,
remindMe: RemindUser
): AuthenticationWorkflowResult => {
const f = Utils.pipe<IdentificationVector, AuthenticationWorkflowResult>(
ident,
authe,
autho,
normalize
);
return f(toAuthenticationVector(login, password, remindMe));
};
};
}
const apiScopes = ["resources:write"];
const login = "root";
const password = "demodemo";
const remindUser = Iam.REMIND_USER;
const authenticateUser = Iam.getAuthenticationWorkflow(
{
identify: Iam.identify,
authenticate: Iam.authenticate,
authorize: Iam.authorize,
},
apiScopes
);
const r = authenticateUser(login, password, remindUser);
console.log(r);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment