@withstudiocms/auth-kit 0.1.0-beta.5 → 0.1.0

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/dist/errors.d.ts CHANGED
@@ -155,4 +155,12 @@ export declare const useUserError: <A>(_try: () => A) => Effect.Effect<A, UserEr
155
155
  * @returns An `Effect` that resolves with the value of type `A` or fails with a `UserError`.
156
156
  */
157
157
  export declare const useUserErrorPromise: <A>(_try: () => Promise<A>) => Effect.Effect<A, UserError>;
158
+ declare const AuthKitError_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 & {
159
+ readonly _tag: "AuthKitError";
160
+ } & Readonly<A>;
161
+ export declare class AuthKitError extends AuthKitError_base<{
162
+ message: string;
163
+ cause?: unknown;
164
+ }> {
165
+ }
158
166
  export {};
package/dist/errors.js CHANGED
@@ -43,7 +43,10 @@ const useUserErrorPromise = (_try) => Effect.tryPromise({
43
43
  try: _try,
44
44
  catch: (cause) => new UserError({ cause })
45
45
  });
46
+ class AuthKitError extends Data.TaggedError("AuthKitError") {
47
+ }
46
48
  export {
49
+ AuthKitError,
47
50
  CheckIfUnsafeError,
48
51
  DecryptionError,
49
52
  EncryptionError,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Effect } from '@withstudiocms/effect';
2
2
  import { AuthKitOptions, PasswordModConfigFinal, type RawAuthKitConfig } from './config.js';
3
+ import { AuthKitError } from './errors.js';
3
4
  export { Password } from './modules/password.js';
4
5
  /**
5
6
  * Creates a scrypt password hashing utility using the provided scrypt configuration.
@@ -9,7 +10,7 @@ export { Password } from './modules/password.js';
9
10
  */
10
11
  export declare const makeScrypt: (config: PasswordModConfigFinal) => Effect.Effect<Effect.Effect<{
11
12
  run: (password: import("crypto").BinaryLike) => Effect.Effect<Buffer<ArrayBufferLike>, import("@withstudiocms/effect/scrypt").ScryptError, never>;
12
- }, never, never>, Error, never>;
13
+ }, never, never>, AuthKitError, never>;
13
14
  declare const AuthKit_base: Effect.Service.Class<AuthKit, "@withstudiocms/AuthKit", {
14
15
  readonly effect: Effect.Effect<{
15
16
  readonly Encryption: Effect.Effect<{
@@ -36,55 +37,19 @@ declare const AuthKit_base: Effect.Service.Class<AuthKit, "@withstudiocms/AuthKi
36
37
  readonly User: Effect.Effect<{
37
38
  readonly verifyUsernameInput: (username: string) => Effect.Effect<string | true, import("./errors.js").CheckIfUnsafeError | import("./errors.js").UserError, never>;
38
39
  readonly createUserAvatar: (email: string) => Effect.Effect<string, import("./errors.js").UserError, never>;
39
- readonly createLocalUser: (name: string, username: string, email: string, password: string) => Effect.Effect<{
40
- name: string;
41
- username: string;
42
- id: string;
43
- url: string | null;
44
- email: string | null;
45
- avatar: string | null;
46
- password: string | null;
47
- updatedAt: Date | null;
48
- createdAt: Date | null;
49
- emailVerified: boolean;
50
- notifications: string | null;
51
- }, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
52
- readonly createOAuthUser: (data: import("./types.js").UserData, oAuthFields: {
40
+ readonly createLocalUser: (name: string, username: string, email: string, password: string) => Effect.Effect<import("./types.js").UserData, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
41
+ readonly createOAuthUser: (data: import("./types.js").UserDataInsert, oAuthFields: {
53
42
  provider: string;
54
43
  providerUserId: string;
55
- }) => Effect.Effect<{
56
- name: string;
57
- username: string;
58
- id: string;
59
- url: string | null;
60
- email: string | null;
61
- avatar: string | null;
62
- password: string | null;
63
- updatedAt: Date | null;
64
- createdAt: Date | null;
65
- emailVerified: boolean;
66
- notifications: string | null;
67
- }, import("./errors.js").UserError, never>;
68
- readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<{
69
- name: string;
70
- username: string;
71
- id: string;
72
- url: string | null;
73
- email: string | null;
74
- avatar: string | null;
75
- password: string | null;
76
- updatedAt: Date | null;
77
- createdAt: Date | null;
78
- emailVerified: boolean;
79
- notifications: string | null;
80
- }, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
44
+ }) => Effect.Effect<import("./types.js").UserData, import("./errors.js").UserError, never>;
45
+ readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<import("./types.js").UserData, import("@withstudiocms/effect/scrypt").ScryptError | import("./errors.js").UserError, never>;
81
46
  readonly getUserPasswordHash: (userId: string) => Effect.Effect<string, import("./errors.js").UserError, never>;
82
47
  readonly getUserFromEmail: (email: string) => Effect.Effect<import("./types.js").CombinedUserData | null | undefined, import("./errors.js").UserError, never>;
83
48
  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>;
84
49
  readonly getUserPermissionLevel: (userData: import("./types.js").UserSessionData | import("./types.js").CombinedUserData | null) => Effect.Effect<import("./types.js").UserPermissionLevel, import("./errors.js").UserError, never>;
85
50
  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>;
86
51
  }, import("./errors.js").SessionError | import("./errors.js").UserError, never>;
87
- }, Error, AuthKitOptions>;
52
+ }, AuthKitError, AuthKitOptions>;
88
53
  }>;
89
54
  /**
90
55
  * The `AuthKit` service provides a collection of authentication utilities for use within the
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Effect } from "@withstudiocms/effect";
2
2
  import { Scrypt as _Scrypt } from "@withstudiocms/effect/scrypt";
3
3
  import { AuthKitOptions, PasswordModConfigFinal } from "./config.js";
4
+ import { AuthKitError } from "./errors.js";
4
5
  import { _Encryption, _Password, _Session, _User } from "./modules/index.js";
5
6
  import { Password } from "./modules/password.js";
6
7
  const makeScrypt = Effect.fn(
@@ -9,7 +10,10 @@ const makeScrypt = Effect.fn(
9
10
  const { run } = yield* _Scrypt;
10
11
  return { run };
11
12
  }).pipe(Effect.provide(_Scrypt.makeLive(config.scrypt))),
12
- catch: (error) => new Error(`Failed to create Scrypt instance: ${error.message}`)
13
+ catch: (error) => new AuthKitError({
14
+ message: `Failed to create Scrypt instance: ${error.message}`,
15
+ cause: error
16
+ })
13
17
  })
14
18
  );
15
19
  class AuthKit extends Effect.Service()("@withstudiocms/AuthKit", {
@@ -8,21 +8,19 @@ const Session = (config) => Effect.gen(function* () {
8
8
  ...config
9
9
  };
10
10
  if (!sessionTools) {
11
- return yield* Effect.fail(
12
- new SessionError({ cause: "Session tools must be provided in the configuration" })
13
- );
11
+ return yield* new SessionError({
12
+ cause: "Session tools must be provided in the configuration"
13
+ });
14
14
  }
15
15
  if (!Number.isFinite(expTime) || expTime <= 0) {
16
- return yield* Effect.fail(
17
- new SessionError({
18
- cause: "Invalid session config: expTime must be a positive number (ms)"
19
- })
20
- );
16
+ return yield* new SessionError({
17
+ cause: "Invalid session config: expTime must be a positive number (ms)"
18
+ });
21
19
  }
22
20
  if (typeof cookieName !== "string" || cookieName.trim().length === 0) {
23
- return yield* Effect.fail(
24
- new SessionError({ cause: "Invalid session config: cookieName must be a non-empty string" })
25
- );
21
+ return yield* new SessionError({
22
+ cause: "Invalid session config: cookieName must be a non-empty string"
23
+ });
26
24
  }
27
25
  const expTimeHalf = expTime / 2;
28
26
  const defaultSecure = process.env.NODE_ENV === "production";
@@ -55,13 +53,9 @@ const Session = (config) => Effect.gen(function* () {
55
53
  session: null,
56
54
  user: null
57
55
  };
58
- const result = yield* useSessionErrorPromise(
56
+ const userSession = yield* useSessionErrorPromise(
59
57
  () => sessionTools.sessionAndUserData(sessionId)
60
58
  );
61
- if (!result.length) {
62
- return nullSession;
63
- }
64
- const userSession = result[0];
65
59
  if (!userSession) {
66
60
  return nullSession;
67
61
  }
@@ -1,7 +1,7 @@
1
1
  import { Effect } from '@withstudiocms/effect';
2
2
  import type { APIContext, AstroGlobal } from 'astro';
3
3
  import { UserError } from '../errors.js';
4
- import type { CombinedUserData, UserConfig, UserData, UserSessionData } from '../types.js';
4
+ import type { CombinedUserData, UserConfig, UserDataInsert, UserSessionData } from '../types.js';
5
5
  import { UserPermissionLevel } from '../types.js';
6
6
  /**
7
7
  * Factory function to create user-related operations for authentication and user management.
@@ -35,48 +35,12 @@ import { UserPermissionLevel } from '../types.js';
35
35
  export declare const User: ({ Scrypt, session, userTools }: UserConfig) => Effect.Effect<{
36
36
  readonly verifyUsernameInput: (username: string) => Effect.Effect<string | true, import("../errors.js").CheckIfUnsafeError | UserError, never>;
37
37
  readonly createUserAvatar: (email: string) => Effect.Effect<string, UserError, never>;
38
- readonly createLocalUser: (name: string, username: string, email: string, password: string) => Effect.Effect<{
39
- name: string;
40
- username: string;
41
- id: string;
42
- url: string | null;
43
- email: string | null;
44
- avatar: string | null;
45
- password: string | null;
46
- updatedAt: Date | null;
47
- createdAt: Date | null;
48
- emailVerified: boolean;
49
- notifications: string | null;
50
- }, import("@withstudiocms/effect/scrypt").ScryptError | UserError, never>;
51
- readonly createOAuthUser: (data: UserData, oAuthFields: {
38
+ readonly createLocalUser: (name: string, username: string, email: string, password: string) => Effect.Effect<import("../types.js").UserData, import("@withstudiocms/effect/scrypt").ScryptError | UserError, never>;
39
+ readonly createOAuthUser: (data: UserDataInsert, oAuthFields: {
52
40
  provider: string;
53
41
  providerUserId: string;
54
- }) => Effect.Effect<{
55
- name: string;
56
- username: string;
57
- id: string;
58
- url: string | null;
59
- email: string | null;
60
- avatar: string | null;
61
- password: string | null;
62
- updatedAt: Date | null;
63
- createdAt: Date | null;
64
- emailVerified: boolean;
65
- notifications: string | null;
66
- }, UserError, never>;
67
- readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<{
68
- name: string;
69
- username: string;
70
- id: string;
71
- url: string | null;
72
- email: string | null;
73
- avatar: string | null;
74
- password: string | null;
75
- updatedAt: Date | null;
76
- createdAt: Date | null;
77
- emailVerified: boolean;
78
- notifications: string | null;
79
- }, import("@withstudiocms/effect/scrypt").ScryptError | UserError, never>;
42
+ }) => Effect.Effect<import("../types.js").UserData, UserError, never>;
43
+ readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<import("../types.js").UserData, import("@withstudiocms/effect/scrypt").ScryptError | UserError, never>;
80
44
  readonly getUserPasswordHash: (userId: string) => Effect.Effect<string, UserError, never>;
81
45
  readonly getUserFromEmail: (email: string) => Effect.Effect<CombinedUserData | null | undefined, UserError, never>;
82
46
  readonly getUserData: (context: APIContext<Record<string, any>, Record<string, string | undefined>> | AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>) => Effect.Effect<UserSessionData, import("../errors.js").SessionError | UserError, never>;
@@ -15,7 +15,7 @@ import { Session as _Session } from "./session.js";
15
15
  const User = ({ Scrypt, session, userTools }) => Effect.gen(function* () {
16
16
  const [Password, Session] = yield* Effect.all([_Password(Scrypt), _Session(session)]);
17
17
  if (!userTools) {
18
- return yield* Effect.fail(new UserError({ cause: "User tools are not available" }));
18
+ return yield* new UserError({ cause: "User tools are not available" });
19
19
  }
20
20
  const notifier = userTools.notifier;
21
21
  const verifyUsernameInput = Effect.fn(
@@ -43,7 +43,7 @@ const User = ({ Scrypt, session, userTools }) => Effect.gen(function* () {
43
43
  createUserAvatar(email),
44
44
  useUserError(() => userTools.idGenerator())
45
45
  ]);
46
- const createdAt = /* @__PURE__ */ new Date();
46
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
47
47
  const createdUser = yield* useUserErrorPromise(
48
48
  () => userTools.createLocalUser({
49
49
  id,
@@ -83,8 +83,24 @@ const User = ({ Scrypt, session, userTools }) => Effect.gen(function* () {
83
83
  const updateUserPassword = Effect.fn("@withstudiocms/AuthKit/modules/user.updateUserPassword")(
84
84
  function* (userId, password) {
85
85
  const newHash = yield* Password.hashPassword(password);
86
+ const data = yield* useUserErrorPromise(() => userTools.getUserById(userId));
87
+ if (!data) {
88
+ return yield* new UserError({ cause: "User not found" });
89
+ }
90
+ const { avatar, email, emailVerified, name, notifications, id, username, url } = data;
86
91
  return yield* useUserErrorPromise(
87
- () => userTools.updateLocalUser(userId, { password: newHash })
92
+ () => userTools.updateLocalUser(userId, {
93
+ avatar,
94
+ email,
95
+ emailVerified,
96
+ name,
97
+ notifications,
98
+ id,
99
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
100
+ username,
101
+ url,
102
+ password: newHash
103
+ })
88
104
  );
89
105
  }
90
106
  );
@@ -92,9 +108,8 @@ const User = ({ Scrypt, session, userTools }) => Effect.gen(function* () {
92
108
  "@withstudiocms/AuthKit/modules/user.getUserPasswordHash"
93
109
  )(function* (userId) {
94
110
  const user = yield* useUserErrorPromise(() => userTools.getUserById(userId));
95
- if (!user) return yield* Effect.fail(new UserError({ cause: "User not found" }));
96
- if (!user.password)
97
- return yield* Effect.fail(new UserError({ cause: "User has no password" }));
111
+ if (!user) return yield* new UserError({ cause: "User not found" });
112
+ if (!user.password) return yield* new UserError({ cause: "User has no password" });
98
113
  return user.password;
99
114
  });
100
115
  const getUserFromEmail = Effect.fn("@withstudiocms/AuthKit/modules/user.getUserFromEmail")(
package/dist/types.d.ts CHANGED
@@ -35,17 +35,30 @@ export interface UserSession {
35
35
  * @property notifications - Notification settings or preferences for the user, or null if not set.
36
36
  */
37
37
  export interface UserData {
38
- name: string;
39
- username: string;
40
- id?: string | undefined;
41
- url?: string | null | undefined;
42
- email?: string | null | undefined;
43
- avatar?: string | null | undefined;
44
- password?: string | null | undefined;
45
- updatedAt?: Date | null | undefined;
46
- createdAt?: Date | null | undefined;
47
- emailVerified?: boolean | undefined;
48
- notifications?: string | null | undefined;
38
+ readonly username: string;
39
+ readonly password?: string | null | undefined;
40
+ readonly name: string;
41
+ readonly notifications?: string | null | undefined;
42
+ readonly id: string;
43
+ readonly updatedAt: Date;
44
+ readonly url?: string | null | undefined;
45
+ readonly email?: string | null | undefined;
46
+ readonly avatar?: string | null | undefined;
47
+ readonly createdAt: Date;
48
+ readonly emailVerified: boolean;
49
+ }
50
+ export interface UserDataInsert {
51
+ readonly username: string;
52
+ readonly password?: string | null | undefined;
53
+ readonly name: string;
54
+ readonly notifications?: string | null | undefined;
55
+ readonly id: string;
56
+ readonly updatedAt: string;
57
+ readonly url?: string | null | undefined;
58
+ readonly email?: string | null | undefined;
59
+ readonly avatar?: string | null | undefined;
60
+ readonly createdAt: string | undefined;
61
+ readonly emailVerified: boolean;
49
62
  }
50
63
  /**
51
64
  * Represents the data associated with an OAuth authentication event.
@@ -87,9 +100,6 @@ export interface PermissionsData {
87
100
  user: string;
88
101
  rank: AvailablePermissionRanks;
89
102
  }
90
- type Present<T> = {
91
- [K in keyof T]-?: Exclude<T[K], undefined>;
92
- };
93
103
  /**
94
104
  * Represents the session data for a user.
95
105
  *
@@ -99,7 +109,7 @@ type Present<T> = {
99
109
  */
100
110
  export type UserSessionData = {
101
111
  isLoggedIn: boolean;
102
- user: Present<UserData> | null;
112
+ user: UserData | null;
103
113
  permissionLevel: AvailablePermissionRanks;
104
114
  };
105
115
  /**
@@ -121,7 +131,7 @@ export interface CombinedUserData extends UserData {
121
131
  */
122
132
  export interface SessionAndUserData {
123
133
  session: UserSession;
124
- user: Present<UserData>;
134
+ user: UserData;
125
135
  }
126
136
  /**
127
137
  * Represents the result of validating a session.
@@ -143,9 +153,9 @@ export type SessionValidationResult = SessionAndUserData | {
143
153
  */
144
154
  export interface SessionTools {
145
155
  createSession(params: UserSession): Promise<UserSession>;
146
- sessionAndUserData(sessionId: string): Promise<SessionAndUserData[]>;
156
+ sessionAndUserData(sessionId: string): Promise<SessionAndUserData | undefined>;
147
157
  deleteSession(sessionId: string): Promise<void>;
148
- updateSession(sessionId: string, data: UserSession): Promise<UserSession[]>;
158
+ updateSession(sessionId: string, data: UserSession): Promise<UserSession>;
149
159
  }
150
160
  /**
151
161
  * Configuration options for managing user sessions.
@@ -171,7 +181,7 @@ export interface UserTools {
171
181
  notifier?: {
172
182
  admin(type: 'new_user', message: string): Promise<void>;
173
183
  };
174
- createLocalUser(data: UserData): Promise<Present<UserData>>;
184
+ createLocalUser(data: UserDataInsert): Promise<UserData>;
175
185
  createOAuthUser(data: {
176
186
  provider: string;
177
187
  providerUserId: string;
@@ -181,7 +191,7 @@ export interface UserTools {
181
191
  provider: string;
182
192
  providerUserId: string;
183
193
  }>;
184
- updateLocalUser(id: string, data: Partial<UserData>): Promise<Present<UserData>>;
194
+ updateLocalUser(id: string, data: Omit<UserDataInsert, 'createdAt'>): Promise<UserData>;
185
195
  getUserById(id: string): Promise<CombinedUserData | undefined | null>;
186
196
  getUserByEmail(email: string): Promise<CombinedUserData | undefined | null>;
187
197
  getCurrentPermissions(userId: string): Promise<PermissionsData | undefined | null>;
@@ -217,4 +227,3 @@ export declare enum UserPermissionLevel {
217
227
  }
218
228
  export declare const rankToLevel: Record<AvailablePermissionRanks, UserPermissionLevel>;
219
229
  export declare const levelToRank: Record<UserPermissionLevel, AvailablePermissionRanks>;
220
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withstudiocms/auth-kit",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0",
4
4
  "description": "Utilities for managing authentication",
5
5
  "author": {
6
6
  "name": "withstudiocms",
@@ -59,7 +59,7 @@
59
59
  "@oslojs/binary": "^1.0.0",
60
60
  "@oslojs/crypto": "^1.0.1",
61
61
  "@oslojs/encoding": "^1.1.0",
62
- "@withstudiocms/effect": "0.1.0-beta.6"
62
+ "@withstudiocms/effect": "0.1.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "^22.0.0"
@@ -74,6 +74,8 @@
74
74
  "build": "pnpm update:lists && pnpm buildkit build 'src/**/*.{ts,astro,css,json,png}'",
75
75
  "dev": "pnpm update:lists && pnpm buildkit dev 'src/**/*.{ts,astro,css,json,png}'",
76
76
  "test": "vitest",
77
+ "effect-check": "pnpm effect-language-service diagnostics --project tsconfig.tspc.json",
78
+ "ci:effect-check": "pnpm effect-check --format github-actions",
77
79
  "typecheck": "tspc -p tsconfig.tspc.json"
78
80
  }
79
81
  }