@idevconn/create-icore 0.6.3 → 0.7.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 (87) hide show
  1. package/dist/cli.js +384 -276
  2. package/dist/index.cjs +385 -277
  3. package/dist/index.d.cts +3 -3
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +382 -274
  6. package/package.json +1 -1
  7. package/templates/.yarn/releases/yarn-4.16.0.cjs +944 -0
  8. package/templates/.yarnrc.yml +1 -1
  9. package/templates/apps/api/src/app/storage/storage.controller.ts +28 -0
  10. package/templates/apps/microservices/auth/src/app/app.module.ts +20 -2
  11. package/templates/apps/microservices/notes/src/app/app.module.ts +17 -2
  12. package/templates/apps/microservices/upload/src/app/app.module.ts +17 -2
  13. package/templates/apps/microservices/upload/src/app/storage.controller.ts +7 -0
  14. package/templates/apps/templates/client-antd/src/components/auth/AuthBrandPanel.tsx +59 -0
  15. package/templates/apps/templates/client-antd/src/components/auth/CheckEmailScreen.tsx +28 -0
  16. package/templates/apps/templates/client-antd/src/components/auth/LoginForm.tsx +116 -0
  17. package/templates/apps/templates/client-antd/src/components/auth/MagicLinkForm.tsx +95 -0
  18. package/templates/apps/templates/client-antd/src/components/auth/RegisterForm.tsx +98 -0
  19. package/templates/apps/templates/client-antd/src/globals.less +6 -0
  20. package/templates/apps/templates/client-antd/src/main.tsx +1 -1
  21. package/templates/apps/templates/client-antd/src/routes/login.tsx +45 -181
  22. package/templates/apps/templates/client-mui/src/components/auth/AuthBrandPanel.tsx +59 -0
  23. package/templates/apps/templates/client-mui/src/components/auth/CheckEmailScreen.tsx +28 -0
  24. package/templates/apps/templates/client-mui/src/components/auth/LoginForm.tsx +141 -0
  25. package/templates/apps/templates/client-mui/src/components/auth/MagicLinkForm.tsx +106 -0
  26. package/templates/apps/templates/client-mui/src/components/auth/RegisterForm.tsx +113 -0
  27. package/templates/apps/templates/client-mui/src/main.tsx +1 -1
  28. package/templates/apps/templates/client-mui/src/routes/login.tsx +50 -186
  29. package/templates/apps/templates/client-shadcn/src/components/auth/AuthBrandPanel.tsx +52 -0
  30. package/templates/apps/templates/client-shadcn/src/components/auth/CheckEmailScreen.tsx +29 -0
  31. package/templates/apps/templates/client-shadcn/src/components/auth/LoginForm.tsx +161 -0
  32. package/templates/apps/templates/client-shadcn/src/components/auth/MagicLinkForm.tsx +110 -0
  33. package/templates/apps/templates/client-shadcn/src/components/auth/RegisterForm.tsx +107 -0
  34. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutHeader.tsx +31 -10
  35. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +22 -27
  36. package/templates/apps/templates/client-shadcn/src/components/ui/card.tsx +1 -1
  37. package/templates/apps/templates/client-shadcn/src/globals.css +39 -13
  38. package/templates/apps/templates/client-shadcn/src/routes/auth.callback.tsx +1 -1
  39. package/templates/apps/templates/client-shadcn/src/routes/login.tsx +55 -165
  40. package/templates/libs/auth-strategies/mongodb/CHANGELOG.md +8 -0
  41. package/templates/libs/auth-strategies/mongodb/README.md +11 -0
  42. package/templates/libs/auth-strategies/mongodb/eslint.config.mjs +19 -0
  43. package/templates/libs/auth-strategies/mongodb/jest.config.cts +10 -0
  44. package/templates/libs/auth-strategies/mongodb/package.json +16 -0
  45. package/templates/libs/auth-strategies/mongodb/project.json +19 -0
  46. package/templates/libs/auth-strategies/mongodb/src/index.ts +1 -0
  47. package/templates/libs/auth-strategies/mongodb/src/lib/__tests__/mongodb-auth.strategy.unit.test.ts +42 -0
  48. package/templates/libs/auth-strategies/mongodb/src/lib/auth-mongodb.spec.ts +7 -0
  49. package/templates/libs/auth-strategies/mongodb/src/lib/auth-mongodb.ts +3 -0
  50. package/templates/libs/auth-strategies/mongodb/src/lib/mongodb-auth.strategy.ts +188 -0
  51. package/templates/libs/auth-strategies/mongodb/tsconfig.json +23 -0
  52. package/templates/libs/auth-strategies/mongodb/tsconfig.lib.json +10 -0
  53. package/templates/libs/auth-strategies/mongodb/tsconfig.spec.json +16 -0
  54. package/templates/libs/db-strategies/mongodb/CHANGELOG.md +7 -0
  55. package/templates/libs/db-strategies/mongodb/README.md +11 -0
  56. package/templates/libs/db-strategies/mongodb/eslint.config.mjs +19 -0
  57. package/templates/libs/db-strategies/mongodb/jest.config.cts +10 -0
  58. package/templates/libs/db-strategies/mongodb/package.json +14 -0
  59. package/templates/libs/db-strategies/mongodb/project.json +19 -0
  60. package/templates/libs/db-strategies/mongodb/src/index.ts +1 -0
  61. package/templates/libs/db-strategies/mongodb/src/lib/__tests__/mongodb-db.strategy.unit.test.ts +38 -0
  62. package/templates/libs/db-strategies/mongodb/src/lib/mongodb-db.strategy.ts +108 -0
  63. package/templates/libs/db-strategies/mongodb/src/lib/mongodb.spec.ts +7 -0
  64. package/templates/libs/db-strategies/mongodb/src/lib/mongodb.ts +3 -0
  65. package/templates/libs/db-strategies/mongodb/tsconfig.json +23 -0
  66. package/templates/libs/db-strategies/mongodb/tsconfig.lib.json +10 -0
  67. package/templates/libs/db-strategies/mongodb/tsconfig.spec.json +16 -0
  68. package/templates/libs/shared/src/strategies/storage.ts +3 -0
  69. package/templates/libs/storage-strategies/mongodb/CHANGELOG.md +8 -0
  70. package/templates/libs/storage-strategies/mongodb/README.md +11 -0
  71. package/templates/libs/storage-strategies/mongodb/eslint.config.mjs +19 -0
  72. package/templates/libs/storage-strategies/mongodb/jest.config.cts +10 -0
  73. package/templates/libs/storage-strategies/mongodb/package.json +14 -0
  74. package/templates/libs/storage-strategies/mongodb/project.json +19 -0
  75. package/templates/libs/storage-strategies/mongodb/src/index.ts +1 -0
  76. package/templates/libs/storage-strategies/mongodb/src/lib/__tests__/mongodb-storage.strategy.unit.test.ts +38 -0
  77. package/templates/libs/storage-strategies/mongodb/src/lib/mongodb-storage.strategy.ts +93 -0
  78. package/templates/libs/storage-strategies/mongodb/src/lib/storage-mongodb.spec.ts +7 -0
  79. package/templates/libs/storage-strategies/mongodb/src/lib/storage-mongodb.ts +3 -0
  80. package/templates/libs/storage-strategies/mongodb/tsconfig.json +23 -0
  81. package/templates/libs/storage-strategies/mongodb/tsconfig.lib.json +10 -0
  82. package/templates/libs/storage-strategies/mongodb/tsconfig.spec.json +16 -0
  83. package/templates/libs/template-shared/src/lib/i18n/keys.ts +216 -56
  84. package/templates/libs/template-shared/src/lib/stores/theme.store.ts +1 -6
  85. package/templates/libs/upload-client/src/lib/upload-client.service.ts +7 -0
  86. package/templates/tsconfig.base.json +4 -1
  87. package/templates/.yarn/releases/yarn-4.15.0.cjs +0 -940
@@ -1,14 +1,15 @@
1
1
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
2
- import { SyntheticEvent, useState } from 'react';
2
+ import { useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { useAuthStore, useNotify } from '@icore/template-shared';
5
- import { api } from '../main';
6
- import { Button } from '../components/ui/button';
7
- import { Input } from '../components/ui/input';
8
- import { Label } from '../components/ui/label';
9
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
5
+ import { AuthBrandPanel } from '../components/auth/AuthBrandPanel';
6
+ import { LoginForm } from '../components/auth/LoginForm';
7
+ import { RegisterForm } from '../components/auth/RegisterForm';
8
+ import { MagicLinkForm } from '../components/auth/MagicLinkForm';
9
+ import { CheckEmailScreen } from '../components/auth/CheckEmailScreen';
10
+ import { api } from '@/main';
10
11
 
11
- type Mode = 'password' | 'magicLinkRequest' | 'magicLinkSent';
12
+ type Mode = 'login' | 'register' | 'magicLink' | 'checkEmail';
12
13
 
13
14
  function LoginPage() {
14
15
  const { t } = useTranslation();
@@ -16,174 +17,63 @@ function LoginPage() {
16
17
  const notify = useNotify();
17
18
  const setAuth = useAuthStore((s) => s.setAuth);
18
19
 
19
- const [mode, setMode] = useState<Mode>('password');
20
- const [email, setEmail] = useState('');
21
- const [password, setPassword] = useState('');
22
- const [submitting, setSubmitting] = useState(false);
20
+ const [mode, setMode] = useState<Mode>('login');
21
+ const [confirmedEmail, setConfirmedEmail] = useState('');
23
22
 
24
- async function handlePasswordSubmit(e: SyntheticEvent<HTMLFormElement>) {
25
- e.preventDefault();
26
- setSubmitting(true);
27
- try {
28
- const session = await api<{
29
- accessToken: string;
30
- refreshToken: string;
31
- user: { id: string; email: string; role?: string };
32
- }>('/auth/login', {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ email, password }),
36
- });
37
- setAuth(session);
38
- notify.success(t('auth.login'));
39
- await navigate({ to: '/dashboard' });
40
- } catch (err) {
41
- notify.error(err instanceof Error ? err.message : t('error.unknown'));
42
- } finally {
43
- setSubmitting(false);
44
- }
23
+ function handleLoginSuccess(session: {
24
+ accessToken: string;
25
+ refreshToken: string;
26
+ user: { id: string; email: string; role?: string };
27
+ }) {
28
+ setAuth(session);
29
+ notify.success(t('auth.login'));
30
+ void navigate({ to: '/dashboard' });
45
31
  }
46
32
 
47
- async function handleMagicLinkSubmit(e: SyntheticEvent<HTMLFormElement>) {
48
- e.preventDefault();
49
- setSubmitting(true);
50
- try {
51
- await api('/auth/magic-link', {
52
- method: 'POST',
53
- headers: { 'Content-Type': 'application/json' },
54
- body: JSON.stringify({ email }),
55
- });
56
- setMode('magicLinkSent');
57
- } catch (err) {
58
- notify.error(err instanceof Error ? err.message : t('error.unknown'));
59
- } finally {
60
- setSubmitting(false);
61
- }
33
+ function handleRegisterSuccess(email: string) {
34
+ setConfirmedEmail(email);
35
+ setMode('checkEmail');
36
+ }
37
+
38
+ function handleError(msg: string) {
39
+ notify.error(msg);
62
40
  }
63
41
 
64
42
  return (
65
- <main className="bg-background flex min-h-screen items-center justify-center p-6">
66
- <Card className="w-full max-w-sm">
67
- <CardHeader>
68
- <CardTitle>{t('auth.login')}</CardTitle>
69
- <CardDescription>
70
- {t('auth.email')} &amp; {t('auth.password')}
71
- </CardDescription>
72
- </CardHeader>
73
- <CardContent className="space-y-4">
74
- {mode !== 'magicLinkSent' && (
75
- <>
76
- <div className="flex gap-2">
77
- <Button
78
- type="button"
79
- variant={mode === 'password' ? 'default' : 'outline'}
80
- size="sm"
81
- className="flex-1"
82
- onClick={() => setMode('password')}
83
- >
84
- {t('auth.withPassword')}
85
- </Button>
86
- <Button
87
- type="button"
88
- variant={mode === 'magicLinkRequest' ? 'default' : 'outline'}
89
- size="sm"
90
- className="flex-1"
91
- onClick={() => setMode('magicLinkRequest')}
92
- >
93
- {t('auth.withMagicLink')}
94
- </Button>
95
- </div>
96
- <div className="space-y-2">
97
- <Button
98
- type="button"
99
- variant="outline"
100
- className="w-full"
101
- onClick={() => window.location.assign('/api/auth/oauth/google')}
102
- >
103
- {t('auth.continueWithGoogle')}
104
- </Button>
105
- <Button
106
- type="button"
107
- variant="outline"
108
- className="w-full"
109
- onClick={() => window.location.assign('/api/auth/oauth/github')}
110
- >
111
- {t('auth.continueWithGithub')}
112
- </Button>
113
- </div>
114
- </>
115
- )}
43
+ <main className="flex min-h-screen bg-[--color-background]">
44
+ <AuthBrandPanel />
116
45
 
117
- {mode === 'password' && (
118
- <form onSubmit={handlePasswordSubmit} className="space-y-4">
119
- <div className="space-y-2">
120
- <Label htmlFor="email">{t('auth.email')}</Label>
121
- <Input
122
- id="email"
123
- type="email"
124
- value={email}
125
- onChange={(e) => setEmail(e.target.value)}
126
- required
127
- autoComplete="email"
128
- />
129
- </div>
130
- <div className="space-y-2">
131
- <Label htmlFor="password">{t('auth.password')}</Label>
132
- <Input
133
- id="password"
134
- type="password"
135
- value={password}
136
- onChange={(e) => setPassword(e.target.value)}
137
- required
138
- autoComplete="current-password"
139
- />
140
- </div>
141
- <Button type="submit" className="w-full" disabled={submitting}>
142
- {submitting ? t('common.loading') : t('auth.login')}
143
- </Button>
144
- </form>
46
+ <div className="flex flex-1 items-center justify-center p-6 lg:p-12">
47
+ <div className="w-full max-w-sm">
48
+ {mode === 'login' && (
49
+ <LoginForm
50
+ api={api}
51
+ onSuccess={handleLoginSuccess}
52
+ onError={handleError}
53
+ onSwitchToRegister={() => setMode('register')}
54
+ onSwitchToMagicLink={() => setMode('magicLink')}
55
+ />
145
56
  )}
146
-
147
- {mode === 'magicLinkRequest' && (
148
- <form onSubmit={handleMagicLinkSubmit} className="space-y-4">
149
- <div className="space-y-2">
150
- <Label htmlFor="email-ml">{t('auth.email')}</Label>
151
- <Input
152
- id="email-ml"
153
- type="email"
154
- value={email}
155
- onChange={(e) => setEmail(e.target.value)}
156
- required
157
- autoComplete="email"
158
- />
159
- </div>
160
- <Button type="submit" className="w-full" disabled={submitting}>
161
- {submitting ? t('common.loading') : t('auth.sendMagicLink')}
162
- </Button>
163
- </form>
57
+ {mode === 'register' && (
58
+ <RegisterForm
59
+ api={api}
60
+ onSuccess={handleRegisterSuccess}
61
+ onError={handleError}
62
+ onSwitchToLogin={() => setMode('login')}
63
+ />
164
64
  )}
165
-
166
- {mode === 'magicLinkSent' && (
167
- <div className="space-y-3 text-center">
168
- <h3 className="text-lg font-semibold">{t('auth.magicLinkSent')}</h3>
169
- <p className="text-muted-foreground text-sm">
170
- {t('auth.magicLinkSentDescription', { email })}
171
- </p>
172
- <Button
173
- type="button"
174
- variant="outline"
175
- className="w-full"
176
- onClick={() => {
177
- setEmail('');
178
- setMode('magicLinkRequest');
179
- }}
180
- >
181
- {t('auth.magicLinkUseDifferentEmail')}
182
- </Button>
183
- </div>
65
+ {mode === 'magicLink' && (
66
+ <MagicLinkForm
67
+ api={api}
68
+ onError={handleError}
69
+ onSwitchToLogin={() => setMode('login')}
70
+ />
71
+ )}
72
+ {mode === 'checkEmail' && (
73
+ <CheckEmailScreen email={confirmedEmail} onBack={() => setMode('login')} />
184
74
  )}
185
- </CardContent>
186
- </Card>
75
+ </div>
76
+ </div>
187
77
  </main>
188
78
  );
189
79
  }
@@ -0,0 +1,8 @@
1
+ # @icore/auth-mongodb
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 2c29eac: Fix ESLint issues, update dependencies, and add MongoDB configuration examples to .env templates.
8
+ - af27cae: Fix MongoDB review bugs and wire GridFS download: guard model re-registration, fix expiresIn calculation, escape regex in list(), replace `as never` cast, drop non-existent uuid v14 dep. Add downloadBuffer to StorageStrategy interface + MongoDbStorageStrategy impl + upload MS handler + UploadClientService method + GET /api/storage/file gateway endpoint so MongoDB storage downloads actually work.
@@ -0,0 +1,11 @@
1
+ # auth-mongodb
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build auth-mongodb` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test auth-mongodb` to execute the unit tests via [Jest](https://jestjs.io).
@@ -0,0 +1,19 @@
1
+ import baseConfig from '../../../eslint.config.mjs';
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ files: ['**/*.json'],
7
+ rules: {
8
+ '@nx/dependency-checks': [
9
+ 'error',
10
+ {
11
+ ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
12
+ },
13
+ ],
14
+ },
15
+ languageOptions: {
16
+ parser: await import('jsonc-eslint-parser'),
17
+ },
18
+ },
19
+ ];
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ displayName: 'auth-mongodb',
3
+ preset: '../../../jest.preset.js',
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
7
+ },
8
+ moduleFileExtensions: ['ts', 'js', 'html'],
9
+ coverageDirectory: '../../../coverage/libs/auth-strategies/mongodb',
10
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@icore/auth-mongodb",
3
+ "version": "0.0.2",
4
+ "private": true,
5
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "dependencies": {
9
+ "@icore/shared": "workspace:*",
10
+ "bcrypt": "^6.0.0",
11
+ "jsonwebtoken": "^9.0.2",
12
+ "mongodb-memory-server": "^11.2.0",
13
+ "mongoose": "^9.6.3",
14
+ "tslib": "^2.3.0"
15
+ }
16
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "auth-mongodb",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/auth-strategies/mongodb/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "outputPath": "dist/libs/auth-strategies/mongodb",
13
+ "main": "libs/auth-strategies/mongodb/src/index.ts",
14
+ "tsConfig": "libs/auth-strategies/mongodb/tsconfig.lib.json",
15
+ "assets": ["libs/auth-strategies/mongodb/*.md"]
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1 @@
1
+ export * from './lib/mongodb-auth.strategy';
@@ -0,0 +1,42 @@
1
+ import { MongoMemoryServer } from 'mongodb-memory-server';
2
+ import { connect, Connection } from 'mongoose';
3
+ import { MongoDbAuthStrategy } from '../mongodb-auth.strategy';
4
+ import { runAuthContract } from '@icore/shared/testing';
5
+
6
+ describe('MongoDbAuthStrategy', () => {
7
+ let mongod: MongoMemoryServer;
8
+ let connection: Connection;
9
+ let strategy: MongoDbAuthStrategy;
10
+
11
+ beforeAll(async () => {
12
+ mongod = await MongoMemoryServer.create();
13
+ const uri = mongod.getUri();
14
+ const conn = await connect(uri);
15
+ connection = conn.connection;
16
+ }, 30000);
17
+
18
+ afterAll(async () => {
19
+ if (connection) await connection.close();
20
+ if (mongod) await mongod.stop();
21
+ });
22
+
23
+ beforeEach(async () => {
24
+ if (connection.db) {
25
+ const collections = await connection.db.collections();
26
+ for (const collection of collections) {
27
+ await collection.deleteMany({});
28
+ }
29
+ }
30
+ });
31
+
32
+ runAuthContract('mongodb', () => {
33
+ if (!strategy) {
34
+ strategy = new MongoDbAuthStrategy({
35
+ connection,
36
+ jwtSecret: 'test-secret',
37
+ jwtExpiresIn: '1h',
38
+ });
39
+ }
40
+ return strategy;
41
+ });
42
+ });
@@ -0,0 +1,7 @@
1
+ import { authMongodb } from './auth-mongodb';
2
+
3
+ describe('authMongodb', () => {
4
+ it('should work', () => {
5
+ expect(authMongodb()).toEqual('auth-mongodb');
6
+ });
7
+ });
@@ -0,0 +1,3 @@
1
+ export function authMongodb(): string {
2
+ return 'auth-mongodb';
3
+ }
@@ -0,0 +1,188 @@
1
+ import { Connection, Model, Schema } from 'mongoose';
2
+ import * as bcrypt from 'bcrypt';
3
+ import * as jwt from 'jsonwebtoken';
4
+ import { randomUUID } from 'node:crypto';
5
+ import {
6
+ AuthStrategy,
7
+ AuthSession,
8
+ VerifiedToken,
9
+ MagicLinkRequest,
10
+ OAuthProvider,
11
+ OAuthStartResult,
12
+ } from '@icore/shared';
13
+
14
+ export interface MongoDbAuthStrategyOptions {
15
+ connection: Connection;
16
+ jwtSecret: string;
17
+ jwtExpiresIn?: string;
18
+ refreshExpiresIn?: string;
19
+ }
20
+
21
+ interface UserDoc {
22
+ id: string;
23
+ email: string;
24
+ passwordHash?: string;
25
+ role?: string;
26
+ }
27
+
28
+ interface SessionDoc {
29
+ id: string;
30
+ userId: string;
31
+ refreshToken: string;
32
+ expiresAt: Date;
33
+ }
34
+
35
+ function parseDurationSeconds(s: string): number {
36
+ const m = /^(\d+)(s|m|h|d)$/.exec(s);
37
+ if (!m) return 900;
38
+ const n = parseInt(m[1]!, 10);
39
+ const unit = m[2]!;
40
+ if (unit === 's') return n;
41
+ if (unit === 'm') return n * 60;
42
+ if (unit === 'h') return n * 3600;
43
+ return n * 86400;
44
+ }
45
+
46
+ export class MongoDbAuthStrategy implements AuthStrategy {
47
+ private userModel: Model<UserDoc>;
48
+ private sessionModel: Model<SessionDoc>;
49
+
50
+ constructor(private readonly opts: MongoDbAuthStrategyOptions) {
51
+ const userSchema = new Schema<UserDoc>(
52
+ {
53
+ id: { type: String, required: true, unique: true },
54
+ email: { type: String, required: true, unique: true },
55
+ passwordHash: { type: String, required: false },
56
+ role: { type: String, required: false },
57
+ },
58
+ { timestamps: true },
59
+ );
60
+
61
+ const sessionSchema = new Schema<SessionDoc>(
62
+ {
63
+ id: { type: String, required: true, unique: true },
64
+ userId: { type: String, required: true },
65
+ refreshToken: { type: String, required: true, unique: true },
66
+ expiresAt: { type: Date, required: true },
67
+ },
68
+ { timestamps: true },
69
+ );
70
+
71
+ this.userModel =
72
+ (this.opts.connection.models['User'] as Model<UserDoc> | undefined) ??
73
+ this.opts.connection.model<UserDoc>('User', userSchema);
74
+ this.sessionModel =
75
+ (this.opts.connection.models['Session'] as Model<SessionDoc> | undefined) ??
76
+ this.opts.connection.model<SessionDoc>('Session', sessionSchema);
77
+ }
78
+
79
+ async verifyToken(token: string): Promise<VerifiedToken> {
80
+ try {
81
+ const decoded = jwt.verify(token, this.opts.jwtSecret) as jwt.JwtPayload;
82
+ return {
83
+ uid: decoded.sub as string,
84
+ email: decoded['email'] as string,
85
+ role: decoded['role'] as string,
86
+ };
87
+ } catch (err) {
88
+ throw new Error('invalid_token', { cause: err });
89
+ }
90
+ }
91
+
92
+ async signIn(email: string, password: string): Promise<AuthSession> {
93
+ const user = await this.userModel.findOne({ email }).exec();
94
+ if (!user || !user.passwordHash) throw new Error('invalid_credentials');
95
+
96
+ const isValid = await bcrypt.compare(password, user.passwordHash);
97
+ if (!isValid) throw new Error('invalid_credentials');
98
+
99
+ return this.createSession(user);
100
+ }
101
+
102
+ async signUp(email: string, password: string): Promise<AuthSession> {
103
+ const existing = await this.userModel.findOne({ email }).exec();
104
+ if (existing) throw new Error('user_already_exists');
105
+
106
+ const passwordHash = await bcrypt.hash(password, 10);
107
+ const user = await this.userModel.create({
108
+ id: randomUUID(),
109
+ email,
110
+ passwordHash,
111
+ });
112
+
113
+ return this.createSession(user);
114
+ }
115
+
116
+ async refresh(refreshToken: string): Promise<AuthSession> {
117
+ const session = await this.sessionModel.findOne({ refreshToken }).exec();
118
+ if (!session || session.expiresAt < new Date()) {
119
+ if (session) await this.sessionModel.deleteOne({ _id: session._id });
120
+ throw new Error('invalid_refresh_token');
121
+ }
122
+
123
+ const user = await this.userModel.findOne({ id: session.userId }).exec();
124
+ if (!user) throw new Error('user_not_found');
125
+
126
+ await this.sessionModel.deleteOne({ _id: session._id });
127
+ return this.createSession(user);
128
+ }
129
+
130
+ async setRole(uid: string, role: string): Promise<void> {
131
+ await this.userModel.findOneAndUpdate({ id: uid }, { role }).exec();
132
+ }
133
+
134
+ async getRole(uid: string): Promise<string | null> {
135
+ const user = await this.userModel.findOne({ id: uid }).exec();
136
+ return user?.role ?? null;
137
+ }
138
+
139
+ async sendMagicLink(_req: MagicLinkRequest): Promise<void> {
140
+ throw new Error('not_implemented');
141
+ }
142
+
143
+ async verifyMagicLink(_token: string): Promise<AuthSession> {
144
+ throw new Error('not_implemented');
145
+ }
146
+
147
+ async startOAuth(_provider: OAuthProvider, _callbackUrl: string): Promise<OAuthStartResult> {
148
+ throw new Error('not_implemented');
149
+ }
150
+
151
+ async completeOAuth(
152
+ _provider: OAuthProvider,
153
+ _code: string,
154
+ _state: string,
155
+ ): Promise<AuthSession> {
156
+ throw new Error('not_implemented');
157
+ }
158
+
159
+ private async createSession(user: {
160
+ id: string;
161
+ email: string;
162
+ role?: string;
163
+ }): Promise<AuthSession> {
164
+ const accessToken = jwt.sign(
165
+ { sub: user.id, email: user.email, role: user.role },
166
+ this.opts.jwtSecret,
167
+ { expiresIn: (this.opts.jwtExpiresIn as jwt.SignOptions['expiresIn']) || '15m' },
168
+ );
169
+
170
+ const refreshToken = randomUUID();
171
+ const expiresAt = new Date();
172
+ expiresAt.setDate(expiresAt.getDate() + 7); // Default 7 days
173
+
174
+ await this.sessionModel.create({
175
+ id: randomUUID(),
176
+ userId: user.id,
177
+ refreshToken,
178
+ expiresAt,
179
+ });
180
+
181
+ return {
182
+ accessToken,
183
+ refreshToken,
184
+ expiresIn: parseDurationSeconds(this.opts.jwtExpiresIn ?? '15m'),
185
+ user: { id: user.id, email: user.email },
186
+ };
187
+ }
188
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "forceConsistentCasingInFileNames": true,
6
+ "strict": true,
7
+ "importHelpers": true,
8
+ "noImplicitOverride": true,
9
+ "noImplicitReturns": true,
10
+ "noFallthroughCasesInSwitch": true,
11
+ "noPropertyAccessFromIndexSignature": true
12
+ },
13
+ "files": [],
14
+ "include": [],
15
+ "references": [
16
+ {
17
+ "path": "./tsconfig.lib.json"
18
+ },
19
+ {
20
+ "path": "./tsconfig.spec.json"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc",
5
+ "declaration": true,
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*.ts"],
9
+ "exclude": ["jest.config.ts", "jest.config.cts", "src/**/*.spec.ts", "src/**/*.test.ts"]
10
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "moduleResolution": "node10",
7
+ "types": ["jest", "node"]
8
+ },
9
+ "include": [
10
+ "jest.config.ts",
11
+ "jest.config.cts",
12
+ "src/**/*.test.ts",
13
+ "src/**/*.spec.ts",
14
+ "src/**/*.d.ts"
15
+ ]
16
+ }
@@ -0,0 +1,7 @@
1
+ # @icore/db-mongodb
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 2c29eac: Fix ESLint issues, update dependencies, and add MongoDB configuration examples to .env templates.
@@ -0,0 +1,11 @@
1
+ # mongodb
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build mongodb` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test mongodb` to execute the unit tests via [Jest](https://jestjs.io).