@withstudiocms/auth-kit 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ import type { SessionConfig } from '../types.js';
3
+ /**
4
+ * The default configuration for user sessions.
5
+ *
6
+ * @remarks
7
+ * - `expTime` sets the session expiration time in milliseconds (default: 14 days).
8
+ * - `cookieName` specifies the name of the cookie used to store the session.
9
+ *
10
+ * @example
11
+ * // Use the default session configuration
12
+ * app.useSession(defaultSessionConfig);
13
+ */
14
+ export declare const defaultSessionConfig: SessionConfig;
15
+ /**
16
+ * Generates a session ID by hashing the provided token using SHA-256 and encoding it in hexadecimal format.
17
+ */
18
+ export declare const makeSessionId: (token: string) => Effect.Effect.AsEffect<Effect.Effect<string, import("../errors.js").SessionError, never>>;
19
+ /**
20
+ * Generates a new expiration date for a session.
21
+ */
22
+ export declare const makeExpirationDate: (expTime: number) => Effect.Effect.AsEffect<Effect.Effect<Date, import("../errors.js").SessionError, never>>;
@@ -0,0 +1,29 @@
1
+ import { sha256 } from "@oslojs/crypto/sha2";
2
+ import { encodeHexLowerCase } from "@oslojs/encoding";
3
+ import { Effect, pipe } from "@withstudiocms/effect";
4
+ import { useSessionError } from "../errors.js";
5
+ const defaultSessionConfig = {
6
+ expTime: 1e3 * 60 * 60 * 24 * 14,
7
+ cookieName: "auth_session"
8
+ };
9
+ const makeSessionId = Effect.fn(
10
+ (token) => useSessionError(() => {
11
+ if (typeof token !== "string" || token.length === 0) {
12
+ throw new TypeError("Invalid token");
13
+ }
14
+ return pipe(new TextEncoder().encode(token), sha256, encodeHexLowerCase);
15
+ })
16
+ );
17
+ const makeExpirationDate = Effect.fn(
18
+ (expTime) => useSessionError(() => {
19
+ if (!Number.isFinite(expTime) || expTime <= 0) {
20
+ throw new TypeError("Invalid expiration time");
21
+ }
22
+ return new Date(Date.now() + expTime);
23
+ })
24
+ );
25
+ export {
26
+ defaultSessionConfig,
27
+ makeExpirationDate,
28
+ makeSessionId
29
+ };
@@ -0,0 +1,27 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ declare const CheckIfUnsafe_base: Effect.Service.Class<CheckIfUnsafe, "studiocms/virtuals/auth/utils/unsafeCheck/CheckIfUnsafe", {
3
+ readonly effect: Effect.Effect<{
4
+ username: (val: string) => Effect.Effect<boolean, import("../errors.js").CheckIfUnsafeError, never>;
5
+ password: (val: string) => Effect.Effect<boolean, import("../errors.js").CheckIfUnsafeError, never>;
6
+ }, never, never>;
7
+ }>;
8
+ /**
9
+ * A service class that provides utility functions to check if a value is unsafe,
10
+ * such as being a reserved username or a password.
11
+ *
12
+ * @remarks
13
+ * This service uses logging and effect-based programming to perform the checks.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const checkIfUnsafe = CheckIfUnsafe;
18
+ * const isReservedUsername = yield* checkIfUnsafe.username('admin');
19
+ * const isPassword = yield* checkIfUnsafe.password('123456');
20
+ * ```
21
+ *
22
+ * @class
23
+ * @implements {Effect.Service<CheckIfUnsafe>}
24
+ */
25
+ export declare class CheckIfUnsafe extends CheckIfUnsafe_base {
26
+ }
27
+ export {};
@@ -0,0 +1,27 @@
1
+ import { Effect } from "@withstudiocms/effect";
2
+ import { useUnsafeCheckError } from "../errors.js";
3
+ import { isReservedPassword } from "./lists/passwords.js";
4
+ import { isReservedUsername } from "./lists/usernames.js";
5
+ class CheckIfUnsafe extends Effect.Service()(
6
+ "studiocms/virtuals/auth/utils/unsafeCheck/CheckIfUnsafe",
7
+ {
8
+ effect: Effect.gen(function* () {
9
+ const username = (val) => useUnsafeCheckError(
10
+ () => isReservedUsername(val),
11
+ "An unknown Error occurred when checking the username list"
12
+ );
13
+ const password = (val) => useUnsafeCheckError(
14
+ () => isReservedPassword(val),
15
+ "An unknown Error occurred when checking the password list"
16
+ );
17
+ return {
18
+ username,
19
+ password
20
+ };
21
+ })
22
+ }
23
+ ) {
24
+ }
25
+ export {
26
+ CheckIfUnsafe
27
+ };
@@ -0,0 +1,62 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ import { type AvailablePermissionRanks, type CombinedUserData, UserPermissionLevel, type UserSessionData } from '../types.js';
3
+ /**
4
+ * Verifies that the provided username meets the required length constraints.
5
+ *
6
+ * @param username - The username string to validate.
7
+ * @returns A user error message if the username is not between 3 and 32 characters long, otherwise `undefined`.
8
+ */
9
+ export declare const verifyUsernameLength: (username: string) => Effect.Effect<string | undefined, import("../errors.js").UserError, never>;
10
+ /**
11
+ * Verifies that the provided username contains only allowed characters.
12
+ *
13
+ * Allowed characters are:
14
+ * - Lowercase letters (`a-z`)
15
+ * - Numbers (`0-9`)
16
+ * - Hyphens (`-`)
17
+ * - Underscores (`_`)
18
+ *
19
+ * @param username - The username string to validate.
20
+ * @returns An error message if the username contains invalid characters, otherwise `undefined`.
21
+ */
22
+ export declare const verifyUsernameCharacters: (username: string) => Effect.Effect.AsEffect<Effect.Effect<string | undefined, import("../errors.js").UserError, never>>;
23
+ /**
24
+ * Verifies if the provided username is considered unsafe (e.g., commonly used usernames like "admin", "root", etc.).
25
+ *
26
+ * @param username - The username string to be checked for safety.
27
+ * @returns An `Effect` that yields a string with an error message if the username is unsafe,
28
+ * or `undefined` if the username is safe.
29
+ *
30
+ * @remarks
31
+ * This function uses the `CheckIfUnsafe` service to determine if the username is unsafe.
32
+ * It is intended to prevent the use of common or reserved usernames.
33
+ */
34
+ export declare const verifyUsernameSafe: (username: string) => Effect.Effect<string | undefined, import("../errors.js").CheckIfUnsafeError, never>;
35
+ /**
36
+ * Returns a default user session wrapped in an Effect.
37
+ *
38
+ * The default session indicates that the user is not logged in,
39
+ * has no user data, and an 'unknown' permission level.
40
+ *
41
+ * @returns {Effect<UserSessionData>} An Effect containing the default UserSessionData.
42
+ */
43
+ export declare const getDefaultUserSession: () => Effect.Effect.AsEffect<Effect.Effect<UserSessionData, never, never>>;
44
+ /**
45
+ * Determines the user's permission level based on the provided user data.
46
+ *
47
+ * This function checks the `permissionLevel` property if present, otherwise it checks
48
+ * the `permissionsData.rank` property. If neither is available or `userData` is null,
49
+ * it returns `'unknown'`.
50
+ *
51
+ * @param userData - The user session or combined user data object, or null.
52
+ * @returns The user's permission level as an `AvailablePermissionRanks` value, or `'unknown'` if not determinable.
53
+ */
54
+ export declare const getLevel: (userData: UserSessionData | CombinedUserData | null) => Effect.Effect<"owner" | "admin" | "editor" | "visitor" | "unknown", import("../errors.js").UserError, never>;
55
+ /**
56
+ * Parses the given required permission rank and returns the corresponding `UserPermissionLevel`.
57
+ * If the provided permission rank does not match any known value, it returns `UserPermissionLevel.unknown`.
58
+ *
59
+ * @param requiredPerms - The required permission rank to parse. Should be one of the values defined in `AvailablePermissionRanks`.
60
+ * @returns The corresponding `UserPermissionLevel` for the given permission rank.
61
+ */
62
+ export declare const parseRequiredPerms: (requiredPerms: AvailablePermissionRanks) => Effect.Effect<UserPermissionLevel, import("../errors.js").UserError, never>;
@@ -0,0 +1,80 @@
1
+ import { Effect } from "@withstudiocms/effect";
2
+ import { useUserError } from "../errors.js";
3
+ import {
4
+ UserPermissionLevel
5
+ } from "../types.js";
6
+ import { CheckIfUnsafe } from "./unsafeCheck.js";
7
+ const verifyUsernameLength = (username) => useUserError(() => {
8
+ if (username.length < 3 || username.length > 32) {
9
+ return "Username must be between 3 and 32 characters long";
10
+ }
11
+ return void 0;
12
+ });
13
+ const verifyUsernameCharacters = Effect.fn(
14
+ (username) => useUserError(() => {
15
+ if (!/^[a-z0-9_-]+$/.test(username)) {
16
+ return "Username can only contain lowercase letters, numbers, hyphens (-), and underscores (_)";
17
+ }
18
+ return void 0;
19
+ })
20
+ );
21
+ const verifyUsernameSafe = (username) => Effect.gen(function* () {
22
+ const check = yield* CheckIfUnsafe;
23
+ const isUnsafe = yield* check.username(username);
24
+ if (isUnsafe) {
25
+ return "Username should not be a commonly used unsafe username (admin, root, etc.)";
26
+ }
27
+ return void 0;
28
+ }).pipe(Effect.provide(CheckIfUnsafe.Default));
29
+ const getDefaultUserSession = Effect.fn(
30
+ () => Effect.succeed({
31
+ isLoggedIn: false,
32
+ user: null,
33
+ permissionLevel: "unknown"
34
+ })
35
+ );
36
+ const normalizeRank = (v) => {
37
+ switch (v) {
38
+ case "owner":
39
+ case "admin":
40
+ case "editor":
41
+ case "visitor":
42
+ case "unknown":
43
+ return v;
44
+ default:
45
+ return "unknown";
46
+ }
47
+ };
48
+ const getLevel = (userData) => useUserError(() => {
49
+ if (!userData) return "unknown";
50
+ let userPermissionLevel = "unknown";
51
+ if ("permissionLevel" in userData) {
52
+ userPermissionLevel = normalizeRank(userData.permissionLevel);
53
+ }
54
+ if ("permissionsData" in userData && userData.permissionsData?.rank) {
55
+ userPermissionLevel = normalizeRank(userData.permissionsData.rank);
56
+ }
57
+ return userPermissionLevel;
58
+ });
59
+ const parseRequiredPerms = (requiredPerms) => useUserError(() => {
60
+ switch (requiredPerms) {
61
+ case "owner":
62
+ return UserPermissionLevel.owner;
63
+ case "admin":
64
+ return UserPermissionLevel.admin;
65
+ case "editor":
66
+ return UserPermissionLevel.editor;
67
+ case "visitor":
68
+ return UserPermissionLevel.visitor;
69
+ default:
70
+ return UserPermissionLevel.unknown;
71
+ }
72
+ });
73
+ export {
74
+ getDefaultUserSession,
75
+ getLevel,
76
+ parseRequiredPerms,
77
+ verifyUsernameCharacters,
78
+ verifyUsernameLength,
79
+ verifyUsernameSafe
80
+ };
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@withstudiocms/auth-kit",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Utilities for managing authentication",
5
+ "author": {
6
+ "name": "withstudiocms",
7
+ "url": "https://studiocms.dev"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/withstudiocms/studiocms.git",
12
+ "directory": "packages/@withstudiocms/auth-kit"
13
+ },
14
+ "contributors": [
15
+ "Adammatthiesen",
16
+ "jdtjenkins",
17
+ "dreyfus92",
18
+ "code.spirit"
19
+ ],
20
+ "license": "MIT",
21
+ "keywords": [
22
+ "astro-studiocms",
23
+ "cms",
24
+ "studiocms"
25
+ ],
26
+ "homepage": "https://studiocms.dev",
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "provenance": true
30
+ },
31
+ "sideEffects": false,
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "./config": {
41
+ "types": "./dist/config.d.ts",
42
+ "default": "./dist/config.js"
43
+ },
44
+ "./errors": {
45
+ "types": "./dist/errors.d.ts",
46
+ "default": "./dist/errors.js"
47
+ },
48
+ "./types": {
49
+ "types": "./dist/types.d.ts",
50
+ "default": "./dist/types.js"
51
+ },
52
+ "./utils/*": {
53
+ "types": "./dist/utils/*.d.ts",
54
+ "default": "./dist/utils/*.js"
55
+ }
56
+ },
57
+ "type": "module",
58
+ "dependencies": {
59
+ "@oslojs/binary": "^1.0.0",
60
+ "@oslojs/crypto": "^1.0.1",
61
+ "@oslojs/encoding": "^1.1.0",
62
+ "@withstudiocms/effect": "0.1.0-beta.2"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22.0.0"
66
+ },
67
+ "peerDependencies": {
68
+ "astro": "^5.12.9"
69
+ },
70
+ "scripts": {
71
+ "update:username-list": "node ./scripts/update-usernames.js",
72
+ "update:password-list": "node ./scripts/update-passwords.js",
73
+ "update:lists": "pnpm update:username-list && pnpm update:password-list",
74
+ "build": "pnpm update:lists && pnpm buildkit build 'src/**/*.{ts,astro,css,json,png}'",
75
+ "dev": "pnpm update:lists && pnpm buildkit dev 'src/**/*.{ts,astro,css,json,png}'",
76
+ "test": "buildkit test 'test/**/*.test.js'",
77
+ "typecheck": "tspc -p tsconfig.tspc.json"
78
+ }
79
+ }