@rineex/auth-core 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ import config from '@rineex/eslint-config/base';
2
+ import db from '@rineex/eslint-config/db';
3
+
4
+ export default [
5
+ ...config,
6
+ ...db,
7
+
8
+ {
9
+ rules: {
10
+ '@typescript-eslint/consistent-type-definitions': ['off'],
11
+ '@typescript-eslint/consistent-type-imports': 'off',
12
+ '@typescript-eslint/no-extraneous-class': 'off',
13
+ 'max-params': ['error', 5],
14
+ },
15
+ },
16
+ {
17
+ rules: {
18
+ 'perfectionist/sort-imports': [
19
+ 'error',
20
+ {
21
+ groups: [
22
+ 'builtin', // Node.js built-in modules (e.g., 'fs', 'path')
23
+ 'external', // External dependencies (e.g., 'react', 'lodash')
24
+ 'nestjs',
25
+ 'common', // Your custom group for '@/common/*'
26
+ 'internal', // Other internal imports (e.g., '~/components/Button')
27
+ 'parent', // Parent directory imports (e.g., '../utils')
28
+ 'sibling', // Same directory imports (e.g., './config')
29
+ 'index', // Index file imports (e.g., './')
30
+ ],
31
+ customGroups: {
32
+ value: {
33
+ nestjs: '@nestjs/*',
34
+ common: '@/*', // Matches imports like '@/common/utils', '@/common/helpers', etc.
35
+ },
36
+ },
37
+
38
+ newlinesBetween: 'always',
39
+ type: 'line-length',
40
+
41
+ order: 'desc',
42
+ },
43
+ ],
44
+ 'perfectionist/sort-objects': [
45
+ 'error',
46
+ {
47
+ type: 'line-length',
48
+ order: 'desc',
49
+ },
50
+ ],
51
+
52
+ 'perfectionist/sort-decorators': [
53
+ 'error',
54
+ { type: 'line-length', order: 'desc' },
55
+ ],
56
+ },
57
+ },
58
+ ];
package/package.json CHANGED
@@ -1,49 +1,50 @@
1
1
  {
2
2
  "name": "@rineex/auth-core",
3
- "version": "0.0.1",
4
- "description": "Auth Core package for Rineex core modules",
3
+ "version": "0.0.2",
4
+ "description": "Authentication Core package for Rineex core modules",
5
5
  "author": "Rineex Team",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
- "files": [
10
- "dist/**"
11
- ],
12
- "sideEffects": false,
13
- "devDependencies": {
14
- "@changesets/cli": "2.29.8",
15
- "@types/node": "24.10.4",
16
- "@vitest/ui": "4.0.16",
17
- "tslib": "2.8.1",
18
- "tsup": "8.5.1",
19
- "typescript": "5.9.3",
20
- "vite-tsconfig-paths": "6.0.2",
21
- "vitest": "4.0.16",
22
- "@rineex/eslint-config": "0.0.0",
23
- "@rineex/typescript-config": "0.0.0"
24
- },
25
9
  "keywords": [
26
10
  "rineex",
27
- "auth",
28
11
  "authentication",
12
+ "auth",
29
13
  "social-login",
30
14
  "otp",
31
15
  "passwordless",
32
16
  "sso"
33
17
  ],
34
- "license": "Apache-2.0",
35
18
  "publishConfig": {
36
19
  "provenance": true,
37
20
  "access": "public",
38
21
  "registry": "https://registry.npmjs.org"
39
22
  },
23
+ "devDependencies": {
24
+ "@changesets/cli": "2.29.8",
25
+ "@types/node": "24.10.4",
26
+ "@vitest/ui": "4.0.16",
27
+ "tslib": "2.8.1",
28
+ "tsup": "8.5.1",
29
+ "type-fest": "5.3.1",
30
+ "typescript": "5.9.3",
31
+ "vite-tsconfig-paths": "6.0.2",
32
+ "vitest": "4.0.16",
33
+ "@rineex/eslint-config": "0.0.0",
34
+ "@rineex/typescript-config": "0.0.0"
35
+ },
40
36
  "repository": {
41
37
  "type": "git",
42
38
  "url": "https://github.com/rineex/core.git",
43
- "directory": "packages/auth-core"
39
+ "directory": "packages/authentication/core"
40
+ },
41
+ "license": "Apache-2.0",
42
+ "dependencies": {
43
+ "@rineex/ddd": "1.5.1"
44
44
  },
45
45
  "scripts": {
46
46
  "test": "vitest run --passWithNoTests",
47
+ "test:watch": "vitest --watch --passWithNoTests",
47
48
  "lint": "eslint 'src/**/*.ts'",
48
49
  "check-types": "tsc --noEmit",
49
50
  "build": "tsup"
@@ -0,0 +1,119 @@
1
+ import { AggregateRoot } from '@rineex/ddd';
2
+
3
+ import { AuthenticationSucceededEvent } from '../events/authentication-succeeded.event';
4
+ import { AuthenticationStartedEvent } from '../events/authentication-started.event';
5
+ import { AuthenticationFailedEvent } from '../events/authentication-failed.event';
6
+ import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
7
+ import { AuthStatus } from '../value-objects/auth-status.vo';
8
+ import { AuthMethod } from '../value-objects/auth-method.vo';
9
+ import { IdentityId } from '../value-objects/identity-id.vo';
10
+
11
+ type Props = {
12
+ status: AuthStatus;
13
+ method: AuthMethod;
14
+ identityId?: IdentityId;
15
+ };
16
+
17
+ /**
18
+ * Aggregate Root representing a single authentication attempt.
19
+ *
20
+ * Responsibilities:
21
+ * - Enforce authentication lifecycle invariants
22
+ * - Emit auditable domain events
23
+ * - Prevent invalid state transitions
24
+ *
25
+ * This aggregate DOES NOT:
26
+ * - Perform authentication
27
+ * - Talk to infrastructure
28
+ * - Know about HTTP or sessions
29
+ */
30
+ export class AuthenticationAttempt extends AggregateRoot<Props> {
31
+ private constructor(id: AuthAttemptId, props: Props) {
32
+ super({
33
+ createdAt: new Date(),
34
+ props,
35
+ id,
36
+ });
37
+ }
38
+
39
+ // This "casts" the ID for this specific class only
40
+ public override get id(): AuthAttemptId {
41
+ return super.id as AuthAttemptId;
42
+ }
43
+
44
+ /**
45
+ * Factory method to start a new authentication attempt.
46
+ */
47
+ static start(
48
+ id: AuthAttemptId,
49
+ method: AuthMethod,
50
+ identityId?: IdentityId,
51
+ ): AuthenticationAttempt {
52
+ const attempt = new AuthenticationAttempt(id, {
53
+ status: AuthStatus.create('PENDING'),
54
+ identityId,
55
+ method,
56
+ });
57
+
58
+ attempt.addEvent(new AuthenticationStartedEvent(id, method));
59
+
60
+ return attempt;
61
+ }
62
+
63
+ /**
64
+ * Marks the authentication attempt as successful.
65
+ *
66
+ * @throws Error if already completed
67
+ */
68
+ succeed(): void {
69
+ if (this.props.status.isNot('PENDING')) {
70
+ throw new Error('Authentication attempt already completed');
71
+ }
72
+
73
+ this.updateProps(props => ({
74
+ ...props,
75
+ status: AuthStatus.create('SUCCEEDED'),
76
+ }));
77
+
78
+ const ev = new AuthenticationSucceededEvent(this.id);
79
+
80
+ this.addEvent(ev);
81
+ }
82
+
83
+ /**
84
+ * Marks the authentication attempt as failed.
85
+ *
86
+ * @throws Error if already completed
87
+ */
88
+ fail(reason: string): void {
89
+ if (this.props.status.isNot('PENDING')) {
90
+ throw new Error('Authentication attempt already completed');
91
+ }
92
+
93
+ if (!reason || reason.length < 3) {
94
+ throw new Error('Failure reason must be provided');
95
+ }
96
+
97
+ this.updateProps(props => ({
98
+ ...props,
99
+ status: AuthStatus.create('FAILED'),
100
+ }));
101
+
102
+ this.addEvent(new AuthenticationFailedEvent(this.id));
103
+ }
104
+
105
+ /**
106
+ * Aggregate invariant validation.
107
+ *
108
+ * Called automatically before domain events are added.
109
+ */
110
+ validate(): void {
111
+ if (!this.props.method) {
112
+ throw new Error('AuthenticationAttempt must have a method');
113
+ }
114
+
115
+ if (!this.props.status) {
116
+ throw new Error('AuthenticationAttempt must have a status');
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,13 @@
1
+ import { Entity } from '@rineex/ddd';
2
+
3
+ import { IdentityId } from '../value-objects/identity-id.vo';
4
+
5
+ /**
6
+ * Represents an authenticated or authenticating identity.
7
+ *
8
+ * This entity is intentionally minimal.
9
+ * All profile, permissions, and roles belong elsewhere.
10
+ */
11
+ export class Identity extends Entity<IdentityId> {
12
+ validate(): void {}
13
+ }
@@ -0,0 +1,24 @@
1
+ import { DomainEvent } from '@rineex/ddd';
2
+
3
+ import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
+
5
+ /**
6
+ * Emitted when an authentication attempt failed.
7
+ */
8
+ export class AuthenticationFailedEvent extends DomainEvent {
9
+ public get eventName(): string {
10
+ return 'authentication.auth_attempt.failed';
11
+ }
12
+
13
+ constructor(public readonly attemptId: AuthAttemptId) {
14
+ super({
15
+ payload: {
16
+ attemptId: attemptId.toString(),
17
+ },
18
+ aggregateId: attemptId.toString(),
19
+ id: crypto.randomUUID(),
20
+ occurredAt: Date.now(),
21
+ schemaVersion: 1,
22
+ });
23
+ }
24
+ }
@@ -0,0 +1,29 @@
1
+ import { DomainEvent } from '@rineex/ddd';
2
+
3
+ import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
+ import { AuthMethod } from '../value-objects/auth-method.vo';
5
+
6
+ /**
7
+ * Emitted when an authentication attempt begins.
8
+ */
9
+ export class AuthenticationStartedEvent extends DomainEvent {
10
+ public get eventName(): string {
11
+ return 'authentication.authentication_started';
12
+ }
13
+
14
+ constructor(
15
+ public readonly attemptId: AuthAttemptId,
16
+ public readonly method: AuthMethod,
17
+ ) {
18
+ super({
19
+ payload: {
20
+ attemptId: attemptId.toString(),
21
+ method: method.toString(),
22
+ },
23
+ aggregateId: attemptId.toString(),
24
+ id: crypto.randomUUID(),
25
+ occurredAt: Date.now(),
26
+ schemaVersion: 1,
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,24 @@
1
+ import { DomainEvent } from '@rineex/ddd';
2
+
3
+ import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
+
5
+ /**
6
+ * Emitted when an authentication attempt succeeds.
7
+ */
8
+ export class AuthenticationSucceededEvent extends DomainEvent {
9
+ public get eventName(): string {
10
+ return 'authentication.auth_attempt.succeeded';
11
+ }
12
+
13
+ constructor(public readonly attemptId: AuthAttemptId) {
14
+ super({
15
+ payload: {
16
+ attemptId: attemptId.toString(),
17
+ },
18
+ aggregateId: attemptId.toString(),
19
+ id: crypto.randomUUID(),
20
+ occurredAt: Date.now(),
21
+ schemaVersion: 1,
22
+ });
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ import { UUID } from '@rineex/ddd';
2
+
3
+ /**
4
+ * Represents a globally unique identifier for an authentication attempt.
5
+ *
6
+ * This ID is used for:
7
+ * - Correlation across logs
8
+ * - Event causation
9
+ * - Security auditing
10
+ *
11
+ * Must be generated externally (UUIDv7 recommended).
12
+ */
13
+ export class AuthAttemptId extends UUID {
14
+ protected validate(value: string): void {
15
+ if (!value || value.length < 16) {
16
+ throw new Error('AuthAttemptId must be a valid non-empty identifier');
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { PrimitiveValueObject } from '@rineex/ddd';
2
+
3
+ /**
4
+ * Represents the authentication method requested or used.
5
+ *
6
+ * Examples:
7
+ * - passwordless
8
+ * - otp
9
+ * - oauth
10
+ * - oidc
11
+ * - passkey
12
+ *
13
+ * This is a value object, NOT a strategy.
14
+ */
15
+ export class AuthMethod extends PrimitiveValueObject<string> {
16
+ protected validate(value: string): void {
17
+ if (!value) {
18
+ throw new Error('AuthMethod cannot be empty');
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,38 @@
1
+ import { PrimitiveValueObject } from '@rineex/ddd';
2
+
3
+ type AuthStatusType = 'FAILED' | 'PENDING' | 'SUCCEEDED';
4
+
5
+ /**
6
+ * Represents the lifecycle state of an authentication attempt.
7
+ *
8
+ * This is intentionally restrictive to avoid invalid transitions.
9
+ */
10
+ export class AuthStatus extends PrimitiveValueObject<AuthStatusType> {
11
+ protected validate(value: AuthStatusType): void {
12
+ if (!['FAILED', 'PENDING', 'SUCCEEDED'].includes(value)) {
13
+ throw new Error(`Invalid AuthStatus: ${value}`);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Checks if the current auth status matches the provided value.
19
+ * @param value - The auth status type to compare against
20
+ * @returns True if the current status matches the provided value, false otherwise
21
+ */
22
+ public is(value: AuthStatusType): boolean {
23
+ return this.value === value;
24
+ }
25
+
26
+ /**
27
+ * Checks if the current authentication status is not equal to the provided value.
28
+ * @param value - The authentication status type to compare against
29
+ * @returns True if the current status differs from the provided value, false otherwise
30
+ */
31
+ public isNot(value: AuthStatusType): boolean {
32
+ return this.value !== value;
33
+ }
34
+
35
+ static create(value: AuthStatusType): AuthStatus {
36
+ return new AuthStatus(value);
37
+ }
38
+ }
@@ -0,0 +1,22 @@
1
+ import { PrimitiveValueObject } from '@rineex/ddd';
2
+
3
+ /**
4
+ * Represents the canonical identity identifier in the auth domain.
5
+ *
6
+ * This does NOT imply:
7
+ * - user
8
+ * - account
9
+ * - profile
10
+ *
11
+ * It is intentionally abstract to support:
12
+ * - enterprise SSO
13
+ * - external IdPs
14
+ * - service identities
15
+ */
16
+ export class IdentityId extends PrimitiveValueObject<string> {
17
+ protected validate(value: string): void {
18
+ if (!value || value.length < 8) {
19
+ throw new Error('IdentityId must be a valid identifier');
20
+ }
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { AuthMethod } from '@/domain/value-objects/auth-method.vo';
2
+ import { AuthContext } from '@/types/auth-context.type';
3
+
4
+ /**
5
+ * Contract implemented by authentication method modules.
6
+ *
7
+ * Examples:
8
+ * - OTP
9
+ * - OAuth
10
+ * - Passkeys
11
+ *
12
+ * Auth-core depends on this abstraction, not implementations.
13
+ */
14
+ export type AuthMethodPort = {
15
+ readonly method: AuthMethod;
16
+
17
+ authenticate: (context: AuthContext) => Promise<void>;
18
+ };
@@ -0,0 +1,11 @@
1
+ import { AuthenticationAttempt } from '@/domain/aggregates/authentication-attempt.aggregate';
2
+
3
+ /**
4
+ * Persistence port for authentication attempts.
5
+ *
6
+ * Implementation is infrastructure-specific.
7
+ */
8
+ export type AuthenticationAttemptRepositoryPort = {
9
+ save: (attempt: AuthenticationAttemptRepositoryPort) => Promise<void>;
10
+ findById: (id: string) => Promise<AuthenticationAttempt | null>;
11
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Context passed across authentication boundaries.
3
+ *
4
+ * This type is intentionally open-ended and extensible.
5
+ */
6
+ export type AuthContext = {
7
+ readonly correlationId: string;
8
+ readonly ipAddress?: string;
9
+ readonly userAgent?: string;
10
+ readonly metadata?: Record<string, unknown>;
11
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+
4
+ "include": ["src/**/*", "tsup.config.ts", "vitest.config.ts"],
5
+ "exclude": ["node_modules", "dist", "test", "**/*.{spec,test}.ts"]
6
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "noImplicitAny": false,
6
+ "noUnusedLocals": false,
7
+ "importHelpers": true,
8
+ "removeComments": false,
9
+ "noLib": false,
10
+ "emitDecoratorMetadata": true,
11
+ "esModuleInterop": true,
12
+ "experimentalDecorators": true,
13
+ "target": "es6",
14
+ "sourceMap": false,
15
+ "outDir": "./dist",
16
+ "rootDir": ".",
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ },
20
+ "lib": ["es7", "ES2021.WeakRef", "ES2022"],
21
+ "types": ["node"]
22
+ },
23
+ "include": ["src/**/*", "tsup.config.ts", "vitest.config.ts"],
24
+ "exclude": ["node_modules"]
25
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig(() => ({
4
+ // eslint-disable-next-line n/no-process-env
5
+ minify: process.env.CI === 'true',
6
+ tsconfig: 'tsconfig.build.json',
7
+ entry: ['src/index.ts'],
8
+ format: ['cjs', 'esm'],
9
+ splitting: false,
10
+ sourcemap: true,
11
+ clean: true,
12
+ dts: true,
13
+ }));
@@ -0,0 +1,12 @@
1
+ import tsconfigPaths from 'vite-tsconfig-paths';
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ include: ['./src/**/*.spec.ts'],
7
+ globals: true,
8
+ root: './',
9
+ },
10
+
11
+ plugins: [tsconfigPaths()],
12
+ });
package/dist/index.d.mts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- var t=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var d=(o,e,x,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of b(e))!c.call(o,p)&&p!==x&&t(o,p,{get:()=>e[p],enumerable:!(r=a(e,p))||r.enumerable});return o};var f=o=>d(t({},"__esModule",{value:!0}),o);var g={};module.exports=f(g);
2
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Auth Core Package for Rineex\n * Provides core authentication and authorization functionality\n */\n\nexport {};\n"],"mappings":"kWAAA,IAAAA,EAAA,kBAAAC,EAAAD","names":["index_exports","__toCommonJS"]}
package/dist/index.mjs DELETED
@@ -1 +0,0 @@
1
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}