@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present StudioCMS - withstudiocms (https://github.com/withstudiocms)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @withstudiocms/auth-kit
2
+
3
+ Authentication Management utilities for StudioCMS
4
+
5
+ ## Example Astro DB Tables
6
+
7
+ The following are example tables that would work with this auth-kit system. The same tables are used within StudioCMS.
8
+
9
+ ```ts
10
+ import { defineTable, column, NOW } from 'astro:db';
11
+
12
+ const Users = defineTable({
13
+ columns: {
14
+ id: column.text({ primaryKey: true }),
15
+ url: column.text({ optional: true }),
16
+ name: column.text(),
17
+ email: column.text({ unique: true, optional: true }),
18
+ avatar: column.text({
19
+ optional: true,
20
+ default: 'https://seccdn.libravatar.org/static/img/mm/80.png',
21
+ }),
22
+ username: column.text(),
23
+ password: column.text({ optional: true }),
24
+ updatedAt: column.date({ default: NOW, optional: true }),
25
+ createdAt: column.date({ default: NOW, optional: true }),
26
+ emailVerified: column.boolean({ default: false }),
27
+ notifications: column.text({ optional: true }),
28
+ },
29
+ });
30
+
31
+ const OAuthAccounts = defineTable({
32
+ columns: {
33
+ providerUserId: column.text({ primaryKey: true }),
34
+ provider: column.text(), // i.e: github, google, discord, auth0 (dynamic to allow new providers on the fly)
35
+ userId: column.text({ references: () => Users.columns.id }),
36
+ },
37
+ });
38
+
39
+ const Sessions = defineTable({
40
+ columns: {
41
+ id: column.text({ primaryKey: true }),
42
+ userId: column.text({ references: () => Users.columns.id, optional: false }),
43
+ expiresAt: column.date(),
44
+ },
45
+ });
46
+
47
+ const Permissions = defineTable({
48
+ columns: {
49
+ user: column.text({ references: () => Users.columns.id }),
50
+ rank: column.text({ enum: ['owner', 'admin', 'editor', 'visitor', 'unknown'] }),
51
+ },
52
+ });
53
+ ```
54
+
55
+ ## License
56
+
57
+ [MIT Licensed](./LICENSE)
@@ -0,0 +1,76 @@
1
+ import { Brand, Context, Layer } from '@withstudiocms/effect';
2
+ import { ScryptConfigOptions } from '@withstudiocms/effect/scrypt';
3
+ import type { SessionConfig, UserTools } from './types.js';
4
+ /**
5
+ * Represents the raw configuration object for the Auth Kit.
6
+ *
7
+ * @property CMS_ENCRYPTION_KEY - The encryption key used by the CMS for securing sensitive data.
8
+ * @property session - The required session configuration settings.
9
+ * @property userTools - The user tools configuration.
10
+ */
11
+ export type RawAuthKitConfig = {
12
+ CMS_ENCRYPTION_KEY: string;
13
+ session: Required<SessionConfig>;
14
+ userTools: UserTools;
15
+ };
16
+ /**
17
+ * Configuration options for the AuthKit module.
18
+ *
19
+ * @property CMS_ENCRYPTION_KEY - The encryption key used for securing CMS data.
20
+ * @property scrypt - Configuration options for the scrypt password hashing algorithm.
21
+ * @property session - Required session configuration options.
22
+ * @property userTools - Tools and utilities related to user management.
23
+ * @remarks
24
+ * This type is branded as 'AuthKitConfig' to provide nominal typing.
25
+ */
26
+ export type AuthKitConfig = {
27
+ CMS_ENCRYPTION_KEY: string;
28
+ scrypt: ScryptConfigOptions;
29
+ session: Required<SessionConfig>;
30
+ userTools: UserTools;
31
+ } & Brand.Brand<'AuthKitConfig'>;
32
+ /**
33
+ * A nominal type for the AuthKit configuration object.
34
+ *
35
+ * This constant uses the `Brand.nominal` utility to create a strongly-typed
36
+ * identifier for the `AuthKitConfig` type, ensuring type safety and preventing
37
+ * accidental misuse of configuration objects.
38
+ *
39
+ * @see AuthKitConfig
40
+ * @see Brand.nominal
41
+ */
42
+ export declare const AuthKitConfig: Brand.Brand.Constructor<AuthKitConfig>;
43
+ declare const AuthKitOptions_base: Context.TagClass<AuthKitOptions, "AuthKitOptions", AuthKitConfig>;
44
+ /**
45
+ * Provides configuration options for the AuthKit module.
46
+ *
47
+ * @remarks
48
+ * This class extends a tagged context for dependency injection and configuration management.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const options = AuthKitOptions.Live({
53
+ * CMS_ENCRYPTION_KEY: 'secret-key',
54
+ * session: { ... },
55
+ * userTools: { ... }
56
+ * });
57
+ * ```
58
+ */
59
+ export declare class AuthKitOptions extends AuthKitOptions_base {
60
+ /**
61
+ * Creates a live instance of `AuthKitOptions` using the provided raw configuration.
62
+ *
63
+ * @param CMS_ENCRYPTION_KEY - The encryption key used for cryptographic operations.
64
+ * @param session - session configuration overrides.
65
+ * @param userTools - Tools or utilities related to user management.
66
+ * @returns A Layer that provides the configured `AuthKitOptions`.
67
+ *
68
+ * @remarks
69
+ * - Scrypt parameters (`N`, `r`, `p`) are read from environment variables (`SCRYPT_N`, `SCRYPT_R`, `SCRYPT_P`),
70
+ * with sensible defaults and minimum values enforced for security.
71
+ * - The session configuration merges defaults with any provided overrides.
72
+ * - The returned Layer can be used for dependency injection in the application.
73
+ */
74
+ static Live: ({ CMS_ENCRYPTION_KEY, session: _session, userTools }: RawAuthKitConfig) => Layer.Layer<AuthKitOptions, never, never>;
75
+ }
76
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,73 @@
1
+ import { Brand, Context, Layer } from "@withstudiocms/effect";
2
+ import { ScryptConfigOptions } from "@withstudiocms/effect/scrypt";
3
+ import { defaultSessionConfig } from "./utils/session.js";
4
+ const AuthKitConfig = Brand.nominal();
5
+ class AuthKitOptions extends Context.Tag("AuthKitOptions")() {
6
+ /**
7
+ * Creates a live instance of `AuthKitOptions` using the provided raw configuration.
8
+ *
9
+ * @param CMS_ENCRYPTION_KEY - The encryption key used for cryptographic operations.
10
+ * @param session - session configuration overrides.
11
+ * @param userTools - Tools or utilities related to user management.
12
+ * @returns A Layer that provides the configured `AuthKitOptions`.
13
+ *
14
+ * @remarks
15
+ * - Scrypt parameters (`N`, `r`, `p`) are read from environment variables (`SCRYPT_N`, `SCRYPT_R`, `SCRYPT_P`),
16
+ * with sensible defaults and minimum values enforced for security.
17
+ * - The session configuration merges defaults with any provided overrides.
18
+ * - The returned Layer can be used for dependency injection in the application.
19
+ */
20
+ static Live = ({ CMS_ENCRYPTION_KEY, session: _session, userTools }) => {
21
+ const normalizedKey = CMS_ENCRYPTION_KEY.trim();
22
+ if (normalizedKey.length === 0) {
23
+ throw new Error("CMS_ENCRYPTION_KEY must be a non-empty base64 string");
24
+ }
25
+ try {
26
+ const raw = typeof Buffer !== "undefined" ? Buffer.from(CMS_ENCRYPTION_KEY, "base64") : new Uint8Array(
27
+ atob(CMS_ENCRYPTION_KEY).split("").map((c) => c.charCodeAt(0))
28
+ );
29
+ if (raw.byteLength !== 16) {
30
+ throw new Error(`CMS_ENCRYPTION_KEY must decode to 16 bytes, got ${raw.byteLength}`);
31
+ }
32
+ } catch {
33
+ throw new Error("CMS_ENCRYPTION_KEY is not valid base64");
34
+ }
35
+ const clamp = (v, min, max) => Number.isSafeInteger(v) ? Math.min(max, Math.max(min, v)) : min;
36
+ const env = (k) => typeof process !== "undefined" && process.env ? process.env[k] : void 0;
37
+ const parsedN = Number.parseInt(env("SCRYPT_N") ?? "", 10);
38
+ const parsedR = Number.parseInt(env("SCRYPT_R") ?? "", 10);
39
+ const parsedP = Number.parseInt(env("SCRYPT_P") ?? "", 10);
40
+ const toPowerOfTwo = (n) => 1 << Math.floor(Math.log2(n));
41
+ const baseN = clamp(parsedN, 16384, 1 << 20);
42
+ const SCRYPT_N = toPowerOfTwo(baseN);
43
+ const SCRYPT_R = clamp(parsedR, 8, 32);
44
+ const SCRYPT_P = clamp(parsedP, 1, 16);
45
+ const scrypt = ScryptConfigOptions({
46
+ encryptionKey: normalizedKey,
47
+ keylen: 64,
48
+ options: {
49
+ N: SCRYPT_N,
50
+ r: SCRYPT_R,
51
+ p: SCRYPT_P
52
+ }
53
+ });
54
+ const session = {
55
+ ...defaultSessionConfig,
56
+ ..._session ?? {}
57
+ };
58
+ if (typeof session.cookieName !== "string" || session.cookieName.trim() === "") {
59
+ throw new Error("session.cookieName must be a non-empty string");
60
+ }
61
+ if (!Number.isSafeInteger(session.expTime) || session.expTime <= 0) {
62
+ throw new Error("session.expTime must be a positive integer (ms)");
63
+ }
64
+ return Layer.succeed(
65
+ this,
66
+ this.of(AuthKitConfig({ CMS_ENCRYPTION_KEY, scrypt, session, userTools }))
67
+ );
68
+ };
69
+ }
70
+ export {
71
+ AuthKitConfig,
72
+ AuthKitOptions
73
+ };
@@ -0,0 +1,158 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ declare const EncryptionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "EncryptionError";
4
+ } & Readonly<A>;
5
+ /**
6
+ * Represents an error that occurs during encryption operations.
7
+ *
8
+ * @extends Data.TaggedError<'EncryptionError'>
9
+ * @template { cause: unknown } - The shape of the error details.
10
+ *
11
+ * @property {unknown} cause - The underlying cause of the encryption error.
12
+ */
13
+ export declare class EncryptionError extends EncryptionError_base<{
14
+ cause: unknown;
15
+ }> {
16
+ }
17
+ declare const DecryptionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
18
+ readonly _tag: "DecryptionError";
19
+ } & Readonly<A>;
20
+ /**
21
+ * Represents an error that occurs during the decryption process.
22
+ *
23
+ * @extends Data.TaggedError
24
+ * @tag DecryptionError
25
+ * @property {unknown} cause - The underlying cause of the decryption failure.
26
+ */
27
+ export declare class DecryptionError extends DecryptionError_base<{
28
+ cause: unknown;
29
+ }> {
30
+ }
31
+ declare const PasswordError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
32
+ readonly _tag: "PasswordError";
33
+ } & Readonly<A>;
34
+ /**
35
+ * PasswordError is a custom error class for handling password-related errors.
36
+ * It extends the TaggedError class from the Data module, allowing for structured error handling.
37
+ */
38
+ export declare class PasswordError extends PasswordError_base<{
39
+ cause?: unknown;
40
+ message?: string;
41
+ }> {
42
+ }
43
+ declare const CheckIfUnsafeError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
44
+ readonly _tag: "CheckIfUnsafeError";
45
+ } & Readonly<A>;
46
+ /**
47
+ * Error thrown when a safety check fails, indicating an unsafe condition.
48
+ *
49
+ * @extends Data.TaggedError
50
+ * @template {object} T - The shape of the error data.
51
+ * @property {string} message - A descriptive message explaining why the error was thrown.
52
+ */
53
+ export declare class CheckIfUnsafeError extends CheckIfUnsafeError_base<{
54
+ message: string;
55
+ }> {
56
+ }
57
+ declare const SessionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
58
+ readonly _tag: "SessionError";
59
+ } & Readonly<A>;
60
+ /**
61
+ * Represents an error related to session handling within the authentication kit.
62
+ *
63
+ * @extends {Data.TaggedError<'SessionError', { cause: unknown }>}
64
+ *
65
+ * @example
66
+ * throw new SessionError({ cause: someError });
67
+ *
68
+ * @property {unknown} cause - The underlying cause of the session error.
69
+ */
70
+ export declare class SessionError extends SessionError_base<{
71
+ cause: unknown;
72
+ }> {
73
+ }
74
+ declare const UserError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
75
+ readonly _tag: "UserError";
76
+ } & Readonly<A>;
77
+ /**
78
+ * Represents an error related to user operations within the authentication kit.
79
+ *
80
+ * @extends Data.TaggedError
81
+ * @template { cause: unknown } - The shape of the error details.
82
+ *
83
+ * @example
84
+ * throw new UserError({ cause: someError });
85
+ */
86
+ export declare class UserError extends UserError_base<{
87
+ cause: unknown;
88
+ }> {
89
+ }
90
+ /**
91
+ * Executes a function within an Effect context, capturing any thrown errors as an `EncryptionError`.
92
+ *
93
+ * @template A - The return type of the function to execute.
94
+ * @param _try - A function that may throw an error.
95
+ * @returns An `Effect` that yields the result of the function or an `EncryptionError` if an exception is thrown.
96
+ */
97
+ export declare const useEncryptionError: <A>(_try: () => A) => Effect.Effect<A, EncryptionError>;
98
+ /**
99
+ * Executes the provided function within an Effect context, mapping any thrown error
100
+ * to a `DecryptionError`.
101
+ *
102
+ * @typeParam A - The type of the value returned by the function.
103
+ * @param _try - A function that may throw an error during execution.
104
+ * @returns An `Effect` that yields the result of the function or a `DecryptionError` if an error is thrown.
105
+ */
106
+ export declare const useDecryptionError: <A>(_try: () => A) => Effect.Effect<A, DecryptionError>;
107
+ /**
108
+ * Executes a function that may throw and wraps any thrown error in a `PasswordError`.
109
+ *
110
+ * @template A - The return type of the function to execute.
111
+ * @param _try - A function that may throw an error.
112
+ * @returns An `Effect` that yields the result of the function or a `PasswordError` if an error is thrown.
113
+ */
114
+ export declare const usePasswordError: <A>(_try: () => A) => Effect.Effect<A, PasswordError>;
115
+ /**
116
+ * Executes a function within an Effect, mapping any thrown error to a `SessionError`.
117
+ *
118
+ * @template A - The return type of the function to execute.
119
+ * @param _try - A function that returns a value of type `A`. If this function throws, the error is caught and wrapped in a `SessionError`.
120
+ * @returns An `Effect` that yields the result of `_try` or fails with a `SessionError` if an error is thrown.
121
+ */
122
+ export declare const useSessionError: <A>(_try: () => A) => Effect.Effect<A, SessionError>;
123
+ /**
124
+ * Wraps an asynchronous function in an Effect that captures any thrown errors
125
+ * and converts them into a `SessionError`.
126
+ *
127
+ * @template A The type of the resolved value from the promise.
128
+ * @param _try - A function that returns a promise to be executed.
129
+ * @returns An `Effect` that resolves with the value of the promise or fails with a `SessionError`.
130
+ */
131
+ export declare const useSessionErrorPromise: <A>(_try: () => Promise<A>) => Effect.Effect<A, SessionError>;
132
+ /**
133
+ * Executes a provided function within an Effect, catching any thrown errors and wrapping them
134
+ * in a `CheckIfUnsafeError` with a prefixed message.
135
+ *
136
+ * @template A - The return type of the function to execute.
137
+ * @param _try - A function to execute that may throw an error.
138
+ * @param prefix - A string to prefix to the error message if an error is caught.
139
+ * @returns An Effect that yields the result of the function or a `CheckIfUnsafeError` if an error occurs.
140
+ */
141
+ export declare const useUnsafeCheckError: <A>(_try: () => A, prefix: string) => Effect.Effect<A, CheckIfUnsafeError>;
142
+ /**
143
+ * Executes a function within an Effect, mapping any thrown error to a `UserError`.
144
+ *
145
+ * @typeParam A - The return type of the function to execute.
146
+ * @param _try - A function to execute that may throw an error.
147
+ * @returns An `Effect` that yields the result of the function or a `UserError` if an error is thrown.
148
+ */
149
+ export declare const useUserError: <A>(_try: () => A) => Effect.Effect<A, UserError>;
150
+ /**
151
+ * Wraps a promise-returning function in an Effect, mapping any thrown error to a `UserError`.
152
+ *
153
+ * @template A The type of the resolved value from the promise.
154
+ * @param _try - A function that returns a promise of type `A`.
155
+ * @returns An `Effect` that resolves with the value of type `A` or fails with a `UserError`.
156
+ */
157
+ export declare const useUserErrorPromise: <A>(_try: () => Promise<A>) => Effect.Effect<A, UserError>;
158
+ export {};
package/dist/errors.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Data, Effect } from "@withstudiocms/effect";
2
+ class EncryptionError extends Data.TaggedError("EncryptionError") {
3
+ }
4
+ class DecryptionError extends Data.TaggedError("DecryptionError") {
5
+ }
6
+ class PasswordError extends Data.TaggedError("PasswordError") {
7
+ }
8
+ class CheckIfUnsafeError extends Data.TaggedError("CheckIfUnsafeError") {
9
+ }
10
+ class SessionError extends Data.TaggedError("SessionError") {
11
+ }
12
+ class UserError extends Data.TaggedError("UserError") {
13
+ }
14
+ const useEncryptionError = (_try) => Effect.try({
15
+ try: _try,
16
+ catch: (cause) => new EncryptionError({ cause })
17
+ });
18
+ const useDecryptionError = (_try) => Effect.try({
19
+ try: _try,
20
+ catch: (cause) => new DecryptionError({ cause })
21
+ });
22
+ const usePasswordError = (_try) => Effect.try({
23
+ try: _try,
24
+ catch: (cause) => new PasswordError({ cause })
25
+ });
26
+ const useSessionError = (_try) => Effect.try({
27
+ try: _try,
28
+ catch: (cause) => new SessionError({ cause })
29
+ });
30
+ const useSessionErrorPromise = (_try) => Effect.tryPromise({
31
+ try: _try,
32
+ catch: (cause) => new SessionError({ cause })
33
+ });
34
+ const useUnsafeCheckError = (_try, prefix) => Effect.try({
35
+ try: _try,
36
+ catch: (cause) => new CheckIfUnsafeError({ message: `${prefix}: ${cause}` })
37
+ });
38
+ const useUserError = (_try) => Effect.try({
39
+ try: _try,
40
+ catch: (cause) => new UserError({ cause })
41
+ });
42
+ const useUserErrorPromise = (_try) => Effect.tryPromise({
43
+ try: _try,
44
+ catch: (cause) => new UserError({ cause })
45
+ });
46
+ export {
47
+ CheckIfUnsafeError,
48
+ DecryptionError,
49
+ EncryptionError,
50
+ PasswordError,
51
+ SessionError,
52
+ UserError,
53
+ useDecryptionError,
54
+ useEncryptionError,
55
+ usePasswordError,
56
+ useSessionError,
57
+ useSessionErrorPromise,
58
+ useUnsafeCheckError,
59
+ useUserError,
60
+ useUserErrorPromise
61
+ };
@@ -0,0 +1,129 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ import { AuthKitOptions, type RawAuthKitConfig } from './config.js';
3
+ declare const AuthKit_base: Effect.Service.Class<AuthKit, "@withstudiocms/AuthKit", {
4
+ readonly effect: Effect.Effect<{
5
+ readonly Encryption: Effect.Effect<{
6
+ readonly encrypt: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect.AsEffect<Effect.Effect<Uint8Array<ArrayBufferLike>, import("./errors.js").EncryptionError, never>>;
7
+ readonly encryptToString: (data: string) => Effect.Effect<Uint8Array<ArrayBufferLike>, import("./errors.js").EncryptionError, never>;
8
+ readonly decrypt: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect.AsEffect<Effect.Effect<Uint8Array<ArrayBufferLike>, import("./errors.js").DecryptionError, never>>;
9
+ readonly decryptToString: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect<string, import("./errors.js").DecryptionError, never>;
10
+ }, import("./errors.js").EncryptionError, never>;
11
+ readonly Password: Effect.Effect<{
12
+ readonly hashPassword: (password: string, _salt?: string | undefined) => Effect.Effect<string, import("@withstudiocms/effect/scrypt").ScryptError, never>;
13
+ readonly verifyPasswordHash: (hash: string, password: string) => Effect.Effect<boolean, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").PasswordError, never>;
14
+ readonly verifyPasswordStrength: (pass: string) => Effect.Effect<string | true, import("./errors.js").PasswordError | import("./errors.js").CheckIfUnsafeError | import("@effect/platform/HttpClientError").ResponseError, never>;
15
+ }, never, never>;
16
+ readonly Session: Effect.Effect<{
17
+ readonly generateSessionToken: () => Effect.Effect.AsEffect<Effect.Effect<string, import("./errors.js").SessionError, never>>;
18
+ readonly createSession: (token: string, userId: string) => Effect.Effect<import("./types.js").UserSession, import("./errors.js").SessionError, never>;
19
+ readonly validateSessionToken: (token: string) => Effect.Effect<import("./types.js").SessionValidationResult, import("./errors.js").SessionError, never>;
20
+ readonly invalidateSession: (sessionId: string) => Effect.Effect.AsEffect<Effect.Effect<void, import("./errors.js").SessionError, never>>;
21
+ readonly setSessionTokenCookie: (context: import("astro").APIContext<Record<string, any>, Record<string, string | undefined>> | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, token: string, expiresAt: Date, secure?: boolean | undefined) => Effect.Effect.AsEffect<Effect.Effect<void, import("./errors.js").SessionError, never>>;
22
+ readonly deleteSessionTokenCookie: (context: import("astro").APIContext<Record<string, any>, Record<string, string | undefined>> | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, secure?: boolean | undefined) => Effect.Effect.AsEffect<Effect.Effect<void, import("./errors.js").SessionError, never>>;
23
+ readonly setOAuthSessionTokenCookie: (context: import("astro").APIContext<Record<string, any>, Record<string, string | undefined>> | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, key: string, value: string, secure?: boolean | undefined) => Effect.Effect.AsEffect<Effect.Effect<void, import("./errors.js").SessionError, never>>;
24
+ readonly createUserSession: (userId: string, context: import("astro").APIContext<Record<string, any>, Record<string, string | undefined>> | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, secure?: boolean | undefined) => Effect.Effect<void, import("./errors.js").SessionError, never>;
25
+ }, import("./errors.js").SessionError, never>;
26
+ readonly User: Effect.Effect<{
27
+ readonly verifyUsernameInput: (username: string) => Effect.Effect<string | true, import("./errors.js").CheckIfUnsafeError | import("./errors.js").UserError, never>;
28
+ readonly createUserAvatar: (email: string) => Effect.Effect.AsEffect<Effect.Effect<string, import("./errors.js").UserError, never>>;
29
+ readonly createLocalUser: (name: string, username: string, email: string, password: string) => Effect.Effect<{
30
+ name: string;
31
+ username: string;
32
+ id: string;
33
+ url: string | null;
34
+ email: string | null;
35
+ avatar: string | null;
36
+ password: string | null;
37
+ updatedAt: Date | null;
38
+ createdAt: Date | null;
39
+ emailVerified: boolean;
40
+ notifications: string | null;
41
+ }, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
42
+ readonly createOAuthUser: (data: import("./types.js").UserData, oAuthFields: {
43
+ provider: string;
44
+ providerUserId: string;
45
+ }) => Effect.Effect<{
46
+ name: string;
47
+ username: string;
48
+ id: string;
49
+ url: string | null;
50
+ email: string | null;
51
+ avatar: string | null;
52
+ password: string | null;
53
+ updatedAt: Date | null;
54
+ createdAt: Date | null;
55
+ emailVerified: boolean;
56
+ notifications: string | null;
57
+ }, import("./errors.js").UserError, never>;
58
+ readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<{
59
+ name: string;
60
+ username: string;
61
+ id: string;
62
+ url: string | null;
63
+ email: string | null;
64
+ avatar: string | null;
65
+ password: string | null;
66
+ updatedAt: Date | null;
67
+ createdAt: Date | null;
68
+ emailVerified: boolean;
69
+ notifications: string | null;
70
+ }, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
71
+ readonly getUserPasswordHash: (userId: string) => Effect.Effect<string, import("./errors.js").UserError, never>;
72
+ readonly getUserFromEmail: (email: string) => Effect.Effect.AsEffect<Effect.Effect<import("./types.js").CombinedUserData | null | undefined, import("./errors.js").UserError, never>>;
73
+ readonly getUserData: (context: import("astro").APIContext<Record<string, any>, Record<string, string | undefined>> | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>) => Effect.Effect<import("./types.js").UserSessionData, import("./errors.js").SessionError | import("./errors.js").UserError, never>;
74
+ readonly getUserPermissionLevel: (userData: import("./types.js").UserSessionData | import("./types.js").CombinedUserData | null) => Effect.Effect<import("./types.js").UserPermissionLevel, import("./errors.js").UserError, never>;
75
+ readonly isUserAllowed: (userData: import("./types.js").UserSessionData | import("./types.js").CombinedUserData | null, requiredPerms: "owner" | "admin" | "editor" | "visitor" | "unknown") => Effect.Effect<boolean, import("./errors.js").UserError, never>;
76
+ }, import("./errors.js").SessionError | import("./errors.js").UserError, never>;
77
+ }, never, AuthKitOptions>;
78
+ }>;
79
+ /**
80
+ * The `AuthKit` service provides a collection of authentication utilities for use within the
81
+ * @withstudiocms ecosystem. It exposes encryption, password management, session management,
82
+ * and user management utilities, all configured via dependency injection.
83
+ *
84
+ * @remarks
85
+ * This service is built on top of the Effect system, allowing for composable and testable
86
+ * authentication logic. Each utility is wrapped with tracing spans for observability.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const authKit = AuthKit.makeConfig({
91
+ * CMS_ENCRYPTION_KEY: 'secret-key',
92
+ * session: ...,
93
+ * userTools: ...
94
+ * });
95
+ * ```
96
+ *
97
+ * @property Encryption - Provides encryption and decryption utilities using the configured key.
98
+ * @property Password - Utilities for password hashing and verification using Scrypt.
99
+ * @property Session - Session management utilities for handling user sessions.
100
+ * @property User - User management utilities, including authentication and user lookup.
101
+ *
102
+ * @method static makeConfig
103
+ * Creates a live configuration for the AuthKit service using the provided raw configuration.
104
+ *
105
+ * @see AuthKitOptions
106
+ * @see _Scrypt
107
+ * @see _Encryption
108
+ * @see _Password
109
+ * @see _Session
110
+ * @see _User
111
+ */
112
+ export declare class AuthKit extends AuthKit_base {
113
+ /**
114
+ * Creates a live instance of `AuthKitOptions` using the provided raw configuration.
115
+ *
116
+ * @param CMS_ENCRYPTION_KEY - The encryption key used for cryptographic operations.
117
+ * @param session - session configuration overrides.
118
+ * @param userTools - Tools or utilities related to user management.
119
+ * @returns A Layer that provides the configured `AuthKitOptions`.
120
+ *
121
+ * @remarks
122
+ * - Scrypt parameters (`N`, `r`, `p`) are read from environment variables (`SCRYPT_N`, `SCRYPT_R`, `SCRYPT_P`),
123
+ * with sensible defaults and minimum values enforced for security.
124
+ * - The session configuration merges defaults with any provided overrides.
125
+ * - The returned Layer can be used for dependency injection in the application.
126
+ */
127
+ static makeConfig: (config: RawAuthKitConfig) => import("effect/Layer").Layer<AuthKitOptions, never, never>;
128
+ }
129
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import { Effect } from "@withstudiocms/effect";
2
+ import { Scrypt as _Scrypt } from "@withstudiocms/effect/scrypt";
3
+ import { AuthKitOptions } from "./config.js";
4
+ import { _Encryption, _Password, _Session, _User } from "./modules/index.js";
5
+ class AuthKit extends Effect.Service()("@withstudiocms/AuthKit", {
6
+ effect: Effect.gen(function* () {
7
+ const { CMS_ENCRYPTION_KEY, scrypt, session, userTools } = yield* AuthKitOptions;
8
+ const Scrypt = Effect.withSpan("@withstudiocms/AuthKit.Scrypt")(
9
+ Effect.gen(function* () {
10
+ const { run } = yield* _Scrypt;
11
+ return { run };
12
+ }).pipe(Effect.provide(_Scrypt.makeLive(scrypt)))
13
+ );
14
+ const Encryption = Effect.withSpan("@withstudiocms/AuthKit.Encryption")(
15
+ _Encryption(CMS_ENCRYPTION_KEY)
16
+ );
17
+ const Password = Effect.withSpan("@withstudiocms/AuthKit.Password")(_Password(Scrypt));
18
+ const Session = Effect.withSpan("@withstudiocms/AuthKit.Session")(_Session(session));
19
+ const User = Effect.withSpan("@withstudiocms/AuthKit.User")(
20
+ _User({ Scrypt, session, userTools })
21
+ );
22
+ return {
23
+ Encryption,
24
+ Password,
25
+ Session,
26
+ User
27
+ };
28
+ })
29
+ }) {
30
+ /**
31
+ * Creates a live instance of `AuthKitOptions` using the provided raw configuration.
32
+ *
33
+ * @param CMS_ENCRYPTION_KEY - The encryption key used for cryptographic operations.
34
+ * @param session - session configuration overrides.
35
+ * @param userTools - Tools or utilities related to user management.
36
+ * @returns A Layer that provides the configured `AuthKitOptions`.
37
+ *
38
+ * @remarks
39
+ * - Scrypt parameters (`N`, `r`, `p`) are read from environment variables (`SCRYPT_N`, `SCRYPT_R`, `SCRYPT_P`),
40
+ * with sensible defaults and minimum values enforced for security.
41
+ * - The session configuration merges defaults with any provided overrides.
42
+ * - The returned Layer can be used for dependency injection in the application.
43
+ */
44
+ static makeConfig = (config) => AuthKitOptions.Live(config);
45
+ }
46
+ export {
47
+ AuthKit
48
+ };
@@ -0,0 +1,22 @@
1
+ import { Effect } from '@withstudiocms/effect';
2
+ /**
3
+ * Factory function to create an encryption module using AES-128-GCM.
4
+ *
5
+ * @param CMS_ENCRYPTION_KEY - The base64-encoded encryption key to use for encryption and decryption.
6
+ * @returns An Effect that yields an object containing encryption and decryption utilities:
7
+ * - `encrypt`: Encrypts a Uint8Array and returns the encrypted data as a Uint8Array.
8
+ * - `encryptToString`: Encrypts a string and returns the encrypted data as a Uint8Array.
9
+ * - `decrypt`: Decrypts an encrypted Uint8Array and returns the decrypted data as a Uint8Array.
10
+ * - `decryptToString`: Decrypts an encrypted Uint8Array and returns the decrypted data as a string.
11
+ *
12
+ * @remarks
13
+ * The encrypted data format is: [IV (16 bytes)] + [encrypted content] + [auth tag (16 bytes)].
14
+ * The encryption key must be a valid base64-encoded string suitable for AES-128-GCM.
15
+ * Throws errors if encryption or decryption fails, or if the input data is invalid.
16
+ */
17
+ export declare const Encryption: (CMS_ENCRYPTION_KEY: string) => Effect.Effect<{
18
+ readonly encrypt: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect.AsEffect<Effect.Effect<Uint8Array<ArrayBufferLike>, import("../errors.js").EncryptionError, never>>;
19
+ readonly encryptToString: (data: string) => Effect.Effect<Uint8Array<ArrayBufferLike>, import("../errors.js").EncryptionError, never>;
20
+ readonly decrypt: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect.AsEffect<Effect.Effect<Uint8Array<ArrayBufferLike>, import("../errors.js").DecryptionError, never>>;
21
+ readonly decryptToString: (data: Uint8Array<ArrayBufferLike>) => Effect.Effect<string, import("../errors.js").DecryptionError, never>;
22
+ }, import("../errors.js").EncryptionError, never>;