@boopkit/cap-users 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.
Files changed (58) hide show
  1. package/capability.json +50 -0
  2. package/dist/account-management-service.d.ts +52 -0
  3. package/dist/account-management-service.d.ts.map +1 -0
  4. package/dist/account-management-service.js +140 -0
  5. package/dist/account-management-service.js.map +1 -0
  6. package/dist/account-property-service.d.ts +18 -0
  7. package/dist/account-property-service.d.ts.map +1 -0
  8. package/dist/account-property-service.js +19 -0
  9. package/dist/account-property-service.js.map +1 -0
  10. package/dist/account-service.d.ts +84 -0
  11. package/dist/account-service.d.ts.map +1 -0
  12. package/dist/account-service.js +136 -0
  13. package/dist/account-service.js.map +1 -0
  14. package/dist/auth-middleware.d.ts +50 -0
  15. package/dist/auth-middleware.d.ts.map +1 -0
  16. package/dist/auth-middleware.js +149 -0
  17. package/dist/auth-middleware.js.map +1 -0
  18. package/dist/auth-service.d.ts +112 -0
  19. package/dist/auth-service.d.ts.map +1 -0
  20. package/dist/auth-service.js +575 -0
  21. package/dist/auth-service.js.map +1 -0
  22. package/dist/identity-service.d.ts +48 -0
  23. package/dist/identity-service.d.ts.map +1 -0
  24. package/dist/identity-service.js +98 -0
  25. package/dist/identity-service.js.map +1 -0
  26. package/dist/index.d.ts +27 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +40 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/team-invitation-service.d.ts +61 -0
  31. package/dist/team-invitation-service.d.ts.map +1 -0
  32. package/dist/team-invitation-service.js +237 -0
  33. package/dist/team-invitation-service.js.map +1 -0
  34. package/dist/types.d.ts +92 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +24 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/user-property-service.d.ts +18 -0
  39. package/dist/user-property-service.d.ts.map +1 -0
  40. package/dist/user-property-service.js +19 -0
  41. package/dist/user-property-service.js.map +1 -0
  42. package/dist/user-service.d.ts +47 -0
  43. package/dist/user-service.d.ts.map +1 -0
  44. package/dist/user-service.js +39 -0
  45. package/dist/user-service.js.map +1 -0
  46. package/package.json +31 -0
  47. package/scaffold/prisma/schema/accounts.prisma +47 -0
  48. package/scaffold/prisma/schema/users.prisma +47 -0
  49. package/scaffold/reference/user-auth.ref.md +237 -0
  50. package/scaffold/src/controllers/auth.ts +158 -0
  51. package/scaffold/src/middleware/auth.ts +28 -0
  52. package/scaffold/src/routes/auth.ts +20 -0
  53. package/scaffold/src/services/accounts.service.ts +53 -0
  54. package/scaffold/src/services/users.service.ts +46 -0
  55. package/scaffold/styles/auth.css +120 -0
  56. package/scaffold/views/login.hbs +28 -0
  57. package/scaffold/views/partials/auth-nav.hbs +32 -0
  58. package/scaffold/views/signup.hbs +29 -0
@@ -0,0 +1,28 @@
1
+ /** @boopkit/cap-users Auth middleware — session resolution + trust checking. */
2
+
3
+ import type { Express } from 'express';
4
+ import { createAuthMiddleware } from '@boopkit/cap-users';
5
+
6
+ // Import user services — adjust path if your service wiring is elsewhere
7
+ import UserService from '../services/users.service';
8
+
9
+ const { authService, identityService, userService } = UserService;
10
+
11
+ const { ensureVisitor, addAuthContext, requireAuth } = createAuthMiddleware(
12
+ { authService, identityService, userService },
13
+ {
14
+ cookieName: 'auth_id',
15
+ loginPath: '/login',
16
+ },
17
+ );
18
+
19
+ /**
20
+ * Wire auth middleware into the Express app.
21
+ * Call at preRoutes order 15 (after requestId, before routes).
22
+ */
23
+ export function setupAuth(app: Express): void {
24
+ app.use(ensureVisitor);
25
+ app.use(addAuthContext);
26
+ }
27
+
28
+ export { ensureVisitor, addAuthContext, requireAuth };
@@ -0,0 +1,20 @@
1
+ /** @boopkit/cap-users — Auth routes: signup, login, logout, forget-device. */
2
+
3
+ import { Router } from 'express';
4
+ import { createAuthController, type AuthControllerDeps } from '../controllers/auth';
5
+
6
+ export default function authRoutes(deps: AuthControllerDeps): Router {
7
+ const auth = createAuthController(deps);
8
+ const router = Router();
9
+
10
+ router.get('/signup', auth.showSignup);
11
+ router.post('/signup', auth.handleSignup);
12
+
13
+ router.get('/login', auth.showLogin);
14
+ router.post('/login', auth.handleLogin);
15
+
16
+ router.get('/logout', auth.handleLogout);
17
+ router.get('/logout/forget', auth.handleForgetDevice);
18
+
19
+ return router;
20
+ }
@@ -0,0 +1,53 @@
1
+ /** @boopkit/cap-users — Account service wiring. */
2
+
3
+ import db from '../db';
4
+ import {
5
+ createAccountService,
6
+ createAccountPropertyService,
7
+ createAccountManagementService,
8
+ createTeamInvitationService,
9
+ } from '@boopkit/cap-users';
10
+ import type { BaseAccountInfo } from '@boopkit/cap-users';
11
+ import UserService from './users.service';
12
+
13
+ /**
14
+ * Create typed account service instances wired to Prisma models.
15
+ *
16
+ * To extend with project-specific account fields, define your own interface:
17
+ *
18
+ * interface MyAccountInfo extends BaseAccountInfo {
19
+ * plan: 'free' | 'pro' | 'enterprise';
20
+ * }
21
+ *
22
+ * Then replace `BaseAccountInfo` below with `MyAccountInfo`.
23
+ */
24
+ const accountService = createAccountService<BaseAccountInfo>(db.account, db.accountUser);
25
+ const accountPropertyService = createAccountPropertyService(db.accountProperty);
26
+
27
+ const accountManagementService = createAccountManagementService({
28
+ accountService,
29
+ userService: UserService.userService,
30
+ accountPropertyService,
31
+ identityService: UserService.identityService,
32
+ });
33
+
34
+ const teamInvitationService = createTeamInvitationService(
35
+ {
36
+ accountPropertyService,
37
+ accountService,
38
+ identityService: UserService.identityService,
39
+ },
40
+ {
41
+ invitationTtl: parseInt(process.env.INVITATION_TTL ?? String(7 * 24 * 60 * 60 * 1000), 10),
42
+ },
43
+ );
44
+
45
+ const AccountService = {
46
+ name: 'accounts' as const,
47
+ accountService,
48
+ accountPropertyService,
49
+ accountManagementService,
50
+ teamInvitationService,
51
+ };
52
+
53
+ export default AccountService;
@@ -0,0 +1,46 @@
1
+ /** @boopkit/cap-users — User service wiring. */
2
+
3
+ import db from '../db';
4
+ import {
5
+ createUserService,
6
+ createIdentityService,
7
+ createUserPropertyService,
8
+ createAuthService,
9
+ } from '@boopkit/cap-users';
10
+ import type { BaseUserInfo } from '@boopkit/cap-users';
11
+
12
+ /**
13
+ * Create typed service instances wired to Prisma models.
14
+ *
15
+ * To extend with project-specific user fields, define your own interface:
16
+ *
17
+ * interface MyUserInfo extends BaseUserInfo {
18
+ * department: string;
19
+ * role: 'admin' | 'member';
20
+ * }
21
+ *
22
+ * Then replace `BaseUserInfo` below with `MyUserInfo`.
23
+ */
24
+ const userService = createUserService<BaseUserInfo>(db.user);
25
+ const identityService = createIdentityService(db.userIdentity);
26
+ const userPropertyService = createUserPropertyService(db.userProperty);
27
+
28
+ const authService = createAuthService(
29
+ { userService, identityService, userPropertyService },
30
+ {
31
+ defaultSessionDuration: parseInt(process.env.SESSION_DURATION ?? '604800', 10),
32
+ hashRounds: 10,
33
+ // onSignup: async (result) => { /* log event, enqueue welcome job, etc. */ },
34
+ // onLogin: async (result) => { /* log event, update last-login, etc. */ },
35
+ },
36
+ );
37
+
38
+ const UserService = {
39
+ name: 'users' as const,
40
+ userService,
41
+ identityService,
42
+ userPropertyService,
43
+ authService,
44
+ };
45
+
46
+ export default UserService;
@@ -0,0 +1,120 @@
1
+ /* @boopkit/cap-users — Semantic styles for auth forms and navigation. */
2
+
3
+ .auth-nav {
4
+ @apply flex w-full items-center justify-end gap-4 px-4 py-2 text-sm;
5
+ }
6
+
7
+ .auth-user {
8
+ @apply flex items-center gap-2;
9
+ }
10
+
11
+ .auth-avatar {
12
+ @apply flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold text-white;
13
+ background-color: var(--color-brand-600);
14
+ }
15
+
16
+ .auth-greeting {
17
+ @apply font-medium text-gray-800;
18
+ }
19
+
20
+ .auth-greeting--recognized {
21
+ @apply font-normal text-gray-600;
22
+ }
23
+
24
+ .auth-actions {
25
+ @apply flex items-center gap-3;
26
+ }
27
+
28
+ .auth-actions--logged-in a {
29
+ @apply text-gray-500 hover:text-gray-700 transition-colors;
30
+ }
31
+
32
+ .auth-link-muted {
33
+ @apply text-gray-400 hover:text-gray-500;
34
+ }
35
+
36
+ .auth-btn {
37
+ @apply rounded-md border border-gray-300 px-3 py-1.5 font-medium text-gray-700
38
+ transition-colors hover:bg-gray-50;
39
+ }
40
+
41
+ .auth-btn--primary {
42
+ border-color: var(--color-brand-600);
43
+ background-color: var(--color-brand-600);
44
+ @apply text-white hover:opacity-90;
45
+ }
46
+
47
+ .auth-btn--primary:hover {
48
+ background-color: var(--color-brand-700);
49
+ border-color: var(--color-brand-700);
50
+ }
51
+
52
+ /* Auth page layout */
53
+ .auth-page {
54
+ @apply flex flex-1 items-center justify-center px-4 py-12;
55
+ }
56
+
57
+ .auth-card {
58
+ @apply w-full max-w-sm rounded-xl bg-white p-8 shadow-md;
59
+ }
60
+
61
+ .auth-header {
62
+ @apply mb-6 text-center;
63
+ }
64
+
65
+ .auth-title {
66
+ @apply text-2xl font-semibold text-gray-900;
67
+ }
68
+
69
+ .auth-subtitle {
70
+ @apply mt-1 text-sm text-gray-500;
71
+ }
72
+
73
+ /* Messages */
74
+ .auth-messages {
75
+ @apply mb-4;
76
+ }
77
+
78
+ .auth-error {
79
+ @apply rounded-md bg-red-50 px-3 py-2 text-sm text-red-700;
80
+ }
81
+
82
+ /* Form */
83
+ .auth-form {
84
+ @apply flex flex-col gap-3;
85
+ }
86
+
87
+ .auth-input {
88
+ @apply w-full rounded-lg border border-gray-300 px-3 py-2 text-sm
89
+ placeholder-gray-400 focus:outline-none focus:ring-1;
90
+ &:focus {
91
+ border-color: var(--color-brand-500);
92
+ --tw-ring-color: var(--color-brand-500);
93
+ }
94
+ }
95
+
96
+ .auth-submit {
97
+ @apply mt-2 w-full rounded-lg px-4 py-2 text-sm font-medium text-white
98
+ focus:outline-none focus:ring-2 focus:ring-offset-2;
99
+ background-color: var(--color-brand-600);
100
+ &:hover {
101
+ background-color: var(--color-brand-700);
102
+ }
103
+ &:focus {
104
+ --tw-ring-color: var(--color-brand-500);
105
+ }
106
+ }
107
+
108
+ .auth-alt {
109
+ @apply mt-4 text-center text-sm text-gray-500;
110
+ }
111
+
112
+ .auth-alt a {
113
+ color: var(--color-brand-600);
114
+ @apply hover:underline;
115
+ }
116
+
117
+ /* Validation errors (signup) */
118
+ .auth-field-error {
119
+ @apply text-xs text-red-600;
120
+ }
@@ -0,0 +1,28 @@
1
+ {{!-- @boopkit/cap-users — Login form. Local password auth only (OAuth deferred). --}}
2
+ <div class="auth-page">
3
+ <section class="auth-card">
4
+ <div class="auth-header">
5
+ <h1 class="auth-title">Log In</h1>
6
+ <p class="auth-subtitle">Welcome back</p>
7
+ </div>
8
+
9
+ {{#if errorMessage}}
10
+ <div class="auth-messages">
11
+ <div class="auth-error">{{errorMessage}}</div>
12
+ </div>
13
+ {{/if}}
14
+
15
+ <form class="auth-form" method="POST" action="{{action}}">
16
+ <input class="auth-input" type="email" name="email" placeholder="Email" autofocus value="{{email}}" required />
17
+ <input class="auth-input" type="password" name="password" placeholder="Password" required />
18
+ {{#if next}}
19
+ <input type="hidden" name="next" value="{{next}}" />
20
+ {{/if}}
21
+ <button class="auth-submit" type="submit">Log In</button>
22
+ </form>
23
+
24
+ <div class="auth-alt">
25
+ Don't have an account? <a href="/signup{{#if next}}?next={{next}}{{/if}}">Sign up</a>
26
+ </div>
27
+ </section>
28
+ </div>
@@ -0,0 +1,32 @@
1
+ {{!-- @boopkit/cap-users — Auth-aware navigation partial.
2
+ Uses res.locals.auth from addAuthContext middleware.
3
+ Trust tiers: null (unknown), 'recognized', 'authenticated', 'verified'. --}}
4
+ <nav class="auth-nav">
5
+ {{#if (eq auth.trustTier "authenticated")}}
6
+ <div class="auth-user">
7
+ <span class="auth-avatar">{{auth.userInitial}}</span>
8
+ <span class="auth-greeting">{{#if auth.userName}}{{auth.userName}}{{else}}{{auth.userEmail}}{{/if}}</span>
9
+ </div>
10
+ <span class="auth-actions auth-actions--logged-in">
11
+ <a href="/logout">Log Out</a>
12
+ </span>
13
+ {{else if (eq auth.trustTier "verified")}}
14
+ <div class="auth-user">
15
+ <span class="auth-avatar">{{auth.userInitial}}</span>
16
+ <span class="auth-greeting">{{#if auth.userName}}{{auth.userName}}{{else}}{{auth.userEmail}}{{/if}}</span>
17
+ </div>
18
+ <span class="auth-actions auth-actions--logged-in">
19
+ <a href="/logout">Log Out</a>
20
+ </span>
21
+ {{else if (eq auth.trustTier "recognized")}}
22
+ <span class="auth-greeting auth-greeting--recognized">Welcome back{{#if auth.userName}}, {{auth.userName}}{{/if}}</span>
23
+ <span class="auth-actions">
24
+ <a href="/login" class="auth-btn">Log In</a>
25
+ </span>
26
+ {{else}}
27
+ <span class="auth-actions">
28
+ <a href="/signup" class="auth-btn auth-btn--primary">Sign Up</a>
29
+ <a href="/login" class="auth-btn">Log In</a>
30
+ </span>
31
+ {{/if}}
32
+ </nav>
@@ -0,0 +1,29 @@
1
+ {{!-- @boopkit/cap-users — Signup form. Local password auth only (OAuth deferred). --}}
2
+ <div class="auth-page">
3
+ <section class="auth-card">
4
+ <div class="auth-header">
5
+ <h1 class="auth-title">Sign Up</h1>
6
+ <p class="auth-subtitle">Create your account</p>
7
+ </div>
8
+
9
+ {{#if errorMessage}}
10
+ <div class="auth-messages">
11
+ <div class="auth-error">{{errorMessage}}</div>
12
+ </div>
13
+ {{/if}}
14
+
15
+ <form class="auth-form" method="POST" action="{{action}}">
16
+ <input class="auth-input" type="email" name="email" placeholder="Email" autofocus value="{{email}}" required />
17
+ <input class="auth-input" type="password" name="password" placeholder="Password" required />
18
+ <input class="auth-input" type="password" name="passwordConfirmation" placeholder="Confirm password" required />
19
+ {{#if next}}
20
+ <input type="hidden" name="next" value="{{next}}" />
21
+ {{/if}}
22
+ <button class="auth-submit" type="submit">Sign Up</button>
23
+ </form>
24
+
25
+ <div class="auth-alt">
26
+ Already have an account? <a href="/login{{#if next}}?next={{next}}{{/if}}">Log in</a>
27
+ </div>
28
+ </section>
29
+ </div>