@cauth/core 0.0.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/README.md +141 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +361 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @cauth/core
|
|
2
|
+
|
|
3
|
+
Core authentication library for Node.js applications with TypeScript support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-Safe Authentication**: Built with TypeScript and Zod validation
|
|
8
|
+
- **JWT-Based Authentication**: Access and refresh token management
|
|
9
|
+
- **Role-Based Access Control**: Flexible role management system
|
|
10
|
+
- **Multi-Factor Authentication**: OTP-based two-factor authentication
|
|
11
|
+
- **Phone Number Support**: E.164 format validation using libphonenumber-js
|
|
12
|
+
- **Error Handling**: Comprehensive error types and handling
|
|
13
|
+
- **Modular Design**: Pluggable database and route contractors
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @cauth/core
|
|
19
|
+
# or
|
|
20
|
+
yarn add @cauth/core
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @cauth/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { CAuth } from '@cauth/core';
|
|
29
|
+
import { PrismaProvider } from '@cauth/prisma';
|
|
30
|
+
import { ExpressContractor } from '@cauth/express';
|
|
31
|
+
|
|
32
|
+
// Initialize authentication system
|
|
33
|
+
const auth = CAuth({
|
|
34
|
+
roles: ['USER', 'ADMIN'],
|
|
35
|
+
dbContractor: new PrismaProvider(prismaClient),
|
|
36
|
+
routeContractor: new ExpressContractor(),
|
|
37
|
+
jwtConfig: {
|
|
38
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
|
|
39
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
|
|
40
|
+
accessTokenLifeSpan: '15m',
|
|
41
|
+
refreshTokenLifeSpan: '7d',
|
|
42
|
+
},
|
|
43
|
+
otpConfig: {
|
|
44
|
+
expiresIn: 300000, // 5 minutes
|
|
45
|
+
length: 6, // 6-digit OTP codes
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Use authentication functions
|
|
50
|
+
const result = await auth.FN.Login({
|
|
51
|
+
email: 'user@example.com',
|
|
52
|
+
//or phoneNumber: '+2659900000'
|
|
53
|
+
password: 'securepassword123',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (result.success) {
|
|
57
|
+
console.log('Login successful:', result.value);
|
|
58
|
+
} else {
|
|
59
|
+
console.log('Login failed:', result.errors); // FNErrors[]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Core Components
|
|
64
|
+
|
|
65
|
+
### Authentication Functions
|
|
66
|
+
|
|
67
|
+
The `FN` namespace provides these authentication functions:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
auth.FN.Login({ email?: string, phoneNumber?: string, password: string })
|
|
71
|
+
auth.FN.Register({ email?: string, phoneNumber?: string, password: string, role: string })
|
|
72
|
+
auth.FN.Logout({ refreshToken: string })
|
|
73
|
+
auth.FN.Refresh({ refreshToken: string })
|
|
74
|
+
auth.FN.ChangePassword({ oldPassword: string, newPassword: string })
|
|
75
|
+
auth.FN.RequestOTPCode({ email?: string, phoneNumber?: string, otpPurpose: OtpPurpose })
|
|
76
|
+
auth.FN.LoginWithOTP({ email?: string, phoneNumber?: string, code: string })
|
|
77
|
+
auth.FN.VerifyOTP({ id: string, code: string, otpPurpose: OtpPurpose })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Token Management
|
|
81
|
+
|
|
82
|
+
The `Tokens` namespace provides these utilities:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
auth.Tokens.GenerateAccessToken(payload: any): Promise<string>
|
|
86
|
+
auth.Tokens.GenerateRefreshToken(payload: any): Promise<string>
|
|
87
|
+
auth.Tokens.GenerateTokenPairs(payload: any): Promise<{ accessToken: string, refreshToken: string }>
|
|
88
|
+
auth.Tokens.VerifyAccessToken<T>(token: string): Promise<T | null>
|
|
89
|
+
auth.Tokens.VerifyRefreshToken<T>(token: string): Promise<T | null>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
The `CAuthOptions` interface defines the configuration:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface CAuthOptions {
|
|
98
|
+
dbContractor: DatabaseContract;
|
|
99
|
+
routeContractor: RoutesContract;
|
|
100
|
+
roles: string[];
|
|
101
|
+
jwtConfig: {
|
|
102
|
+
refreshTokenSecret: string;
|
|
103
|
+
accessTokenSecret: string;
|
|
104
|
+
accessTokenLifeSpan?: string | number; // ms string or number
|
|
105
|
+
refreshTokenLifeSpan?: string | number; // ms string or number
|
|
106
|
+
};
|
|
107
|
+
otpConfig?: {
|
|
108
|
+
expiresIn?: number; // milliseconds, default: 300000 (5 minutes)
|
|
109
|
+
length?: number; // 4-8 digits, default: 6
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Error Types
|
|
115
|
+
|
|
116
|
+
The library provides these error types:
|
|
117
|
+
|
|
118
|
+
- `CredentialMismatchError`: Invalid login credentials
|
|
119
|
+
- `InvalidDataError`: Validation failures
|
|
120
|
+
- `AccountNotFoundError`: Account not found
|
|
121
|
+
- `InvalidRoleError`: Invalid role assignment
|
|
122
|
+
- `InvalidRefreshTokenError`: Invalid/expired refresh token
|
|
123
|
+
- `DuplicateAccountError`: Account already exists
|
|
124
|
+
- `InvalidOTPCode`: Invalid/expired OTP code
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
### Prerequisites
|
|
129
|
+
|
|
130
|
+
- Node.js >= 18
|
|
131
|
+
- TypeScript >= 5.9
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT License - see LICENSE file for details.
|
|
138
|
+
|
|
139
|
+
## Support
|
|
140
|
+
|
|
141
|
+
For issues and feature requests, please visit the [GitHub repository](https://github.com/jonace-mpelule/cauth).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`zod`);c=s(c);let l=require(`libphonenumber-js`);l=s(l);let u=require(`bcrypt`);u=s(u);let d=require(`jsonwebtoken`);d=s(d);var f=class{static ValidationError=`validation-error`;static CredentialError=`credential-error`;static UnKnownError=`unknown-error`;static InvalidDataError=`invalid-data-error`},p=class{static ServerError=`internal-server-error`;static ServerErrorMessage=`Internal server error. We are working to fix this, please try again later`;static InvalidToken=`invalid-token`;static InvalidTokenMessage=`Invalid Token`;static ForbiddenResource=`forbidden-resource`;static ForbiddenResourceMessage=`You don't have sufficient permission for this action`;static InvalidOtp=`invalid-otp`;static InvalidOtpMessage=`Invalid Otp. Please check and try again`;static CredentialMismatch=`credential-mismatch`;static CredentialMismatchMessage=`Credential mismatch. Please check your credentials and try again.`;static InvalidData=`invalid-data`;static InvalidDataMessage=e=>`Invalid Body: ${e}`;static AccountNotFound=`account-not-found`;static AccountNotFoundMessage=`Account not found`;static InvalidRole=`invalid-role`;static InvalidRoleMessage=e=>`Role is invalid, please use one of the following roles: ${e.join(`, `)}`;static InvalidRefreshToken=`invalid-refresh-token`;static InvalidRefreshTokenMessage=`Invalid refresh token`;static DuplicateAccount=`account-already-exists`;static DuplicateAccountMessage=`Account with this credentials already exists`},m=class extends Error{code;static type=f.CredentialError;constructor(){super(p.CredentialMismatchMessage),this.code=p.CredentialMismatch,this.name=`CredentialMismatch`}},h=class extends Error{code;static type=f.ValidationError;constructor(e){super(p.InvalidDataMessage(e)),this.code=p.InvalidData,this.name=`InvalidDataError`}},g=class extends Error{code;static type=f.InvalidDataError;constructor(){super(p.AccountNotFoundMessage),this.code=p.AccountNotFound,this.name=`AccountNotFoundError`}},_=class extends Error{code;static type=f.ValidationError;constructor(e){super(p.InvalidRoleMessage(e)),this.code=p.InvalidRole,this.name=`InvalidRoleError`}},v=class extends Error{code;static type=f.ValidationError;constructor(){super(p.InvalidRefreshTokenMessage),this.code=p.InvalidRefreshToken,this.name=`InvalidRefreshTokenError`}},y=class extends Error{code;static type=f.ValidationError;constructor(){super(p.DuplicateAccountMessage),this.code=p.DuplicateAccount,this.name=`DuplicateAccountError`}},b=class extends Error{code;static type=f.ValidationError;constructor(){super(p.InvalidOtpMessage),this.code=p.InvalidOtp,this.name=`InvalidOTPCode`}},x=class{static LoginPurpose=`LOGIN`;static ResetPasswordPurpose=`RESET_PASSWORD`;static ActionPurpose=`ACTION`};const S=c.z.string().trim().refine(e=>{let t=(0,l.parsePhoneNumberFromString)(e);return!!t&&t.isValid()},{message:`Invalid phone number`}).transform(e=>(0,l.parsePhoneNumberFromString)(e)?.format(`E.164`)??e),C=c.z.object({email:c.z.email(),phoneNumber:c.z.never().optional(),password:c.z.string().min(6)}),w=c.z.object({phoneNumber:S,email:c.z.never().optional(),password:c.z.string().min(6)}),T=c.z.union([C,w]).superRefine((e,t)=>{e.email&&e.phoneNumber&&t.addIssue({code:c.z.ZodIssueCode.custom,message:`Provide either email or phoneNumber`,path:[`email`,`phoneNumber`]})}),E=c.z.object({phoneNumber:S.optional(),email:c.z.email().optional(),role:c.z.string(),password:c.z.string()}),D=E.superRefine((e,t)=>{!e.email&&!e.phoneNumber&&t.addIssue({code:c.z.ZodIssueCode.custom,message:`Provide either email or phoneNumber`,path:[`email`,`phoneNumber`]})}),O=c.z.object({refreshToken:c.z.string()}),k=c.z.object({refreshToken:c.z.string()}),A=c.z.object({accountId:c.z.string(),oldPassword:c.z.string(),newPassword:c.z.string()});function j(e){return`${e?.error?.issues[0].path}: ${e?.error?.issues[0].message}`}function M(e){return{success:!0,value:e}}function N(...e){return{success:!1,errors:e}}async function P({config:e},{...t}){let n=T.safeParse({email:t.email,phoneNumber:t.phoneNumber,password:``});if(!n.success)return N({type:h.type,error:new h(j(n))});let r=await e.dbContractor.findAccountWithCredential({phoneNumber:t.phoneNumber,email:t.email});if(!r||t.usePassword&&!await u.default.compare(String(t.password),String(r?.passwordHash)))return N({type:m.type,error:new m});let i=await e.dbContractor.createOTP({config:e},{id:r.id,purpose:t.otpPurpose});return M({id:r.id,code:i.code})}async function F({config:e,tokens:t},{...n}){let r=T.safeParse({email:n.email,phoneNumber:n.phoneNumber,password:``});if(!r.success)return N({type:h.type,error:new h(j(r))});let i=await e.dbContractor.findAccountWithCredential({email:n.email,phoneNumber:n.phoneNumber});if(!i)return N({type:m.type,error:new m});if(!(await e.dbContractor.verifyOTP({id:i.id,code:n.code,purpose:x.LoginPurpose})).isValid)return N({type:b.type,error:new b});let a=await t.GenerateTokenPairs({id:i.id,role:i.role});return await e.dbContractor.updateAccountLogin({id:i.id,refreshToken:a.refreshToken}),delete i.passwordHash,delete i.refreshTokens,M({account:i,tokens:a})}async function I({config:e},{...t}){return await e.dbContractor.verifyOTP({id:t.id,code:t.code,purpose:t.otpPurpose})}async function L({config:e},{...t}){let n=A.safeParse(t);if(!n.success)return N({type:h.type,error:new h(j(n))});let r=await e.dbContractor.findAccountById({id:t.accountId});if(!r)return N({type:g.type,error:new g});if(!u.default.compare(t.oldPassword,String(r.passwordHash)))return N({type:m.type,error:new m});let i=await u.default.hash(t.newPassword,10);return await e.dbContractor.updateAccount({id:r.id,data:{passwordHash:i}}),M({})}async function R({config:e,tokens:t},{...n}){let r=T.safeParse(n);if(!r.success)return N({type:h.type,error:new h(j(r))});let i=await e.dbContractor.findAccountWithCredential({email:n.email,phoneNumber:n.phoneNumber});if(!i||!await u.default.compare(String(n.password),String(i?.passwordHash)))return N({type:m.type,error:new m});let a=await t.GenerateTokenPairs({id:i.id,role:i.role});return await e.dbContractor.updateAccountLogin({id:i.id,refreshToken:a.refreshToken}),delete i.passwordHash,delete i.refreshTokens,M({account:i,tokens:a})}async function z(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e}}}async function B({config:e,tokens:t},{...n}){let r=k.safeParse(n);if(!r.success)return N({type:h.type,error:new h(j(r))});let i=await z(t.VerifyRefreshToken(n.refreshToken));return i.error||!i?N({type:v.type,error:new v}):(await e.dbContractor.removeAndAddRefreshToken({id:String(i.data?.id),refreshToken:n.refreshToken}),M({}))}async function V({config:e,tokens:t},{...n}){let r=O.safeParse(n);if(!r.success)return N({type:h.type,error:new h(j(r))});let i=await z(t.VerifyRefreshToken(n.refreshToken));if(i.error)return N({type:v.type,error:new v});let a=await e.dbContractor.findAccountById({id:String(i.data?.id)});if(!a)return N({type:g.type,error:new g});if(!a?.refreshTokens?.includes(n.refreshToken))return N({type:v.type,error:new v});let o=await t.GenerateTokenPairs({id:a.id,role:a.role});return await e.dbContractor.removeAndAddRefreshToken({id:a.id,refreshToken:n.refreshToken,newRefreshToken:o.refreshToken}),delete a.refreshTokens,delete a.passwordHash,M({account:a,tokens:o})}async function H({config:e,tokens:t},{...n}){let r=D.safeParse(n);if(!r.success)return N({type:h.type,error:new h(j(r))});if(!e.roles?.includes(n.role))return N({type:_.type,error:new _(e.roles)});if(await e.dbContractor.findAccountWithCredential({email:n.email,phoneNumber:n.phoneNumber}))return N({type:y.type,error:new y});let i=await u.default.hash(n.password,10),a=await e.dbContractor.createAccount({data:{email:n.email,phoneNumber:n.phoneNumber,passwordHash:i,role:n.role,lastLogin:new Date}}),o=await t.GenerateTokenPairs({id:a.id,role:a.role});return await e.dbContractor.updateAccountLogin({id:a.id,refreshToken:o.refreshToken}),M({account:a,tokens:o})}async function U({...e}){return d.default.sign(e.payload,e.config.jwtConfig.accessTokenSecret,{expiresIn:e.config.jwtConfig?.accessTokenLifeSpan??`15m`})}async function W({...e}){return d.default.sign(e.payload,e.config.jwtConfig.refreshTokenSecret,{expiresIn:e.config.jwtConfig?.refreshTokenLifeSpan??`30d`})}async function G({...e}){return{accessToken:d.default.sign(e.payload,e.config.jwtConfig.accessTokenSecret,{expiresIn:e.config.jwtConfig?.accessTokenLifeSpan??`15m`}),refreshToken:d.default.sign(e.payload,e.config.jwtConfig.refreshTokenSecret,{expiresIn:e.config.jwtConfig?.refreshTokenLifeSpan??`30d`})}}async function K({...e}){let t=d.default.verify(e.token,e.config.jwtConfig.refreshTokenSecret);return t instanceof String?null:t}async function q({...e}){let t=d.default.verify(e.token,e.config.jwtConfig.accessTokenSecret);return t instanceof String?null:t}const J=c.default.custom(()=>!0,{message:`Invalid dbContractor: must implement Database Contract interface`}),Y=c.default.custom(()=>!0,{message:`Invalid routeContractor: must implement RoutesContract interface`}),X=c.default.custom(),Z=c.default.object({dbContractor:J,routeContractor:Y,roles:c.default.array(c.default.string()).min(1),jwtConfig:c.default.object({refreshTokenSecret:c.default.string(),accessTokenSecret:c.default.string(),accessTokenLifeSpan:X.optional(),refreshTokenLifeSpan:X.optional()}),otpConfig:c.default.object({expiresIn:c.default.number().optional(),length:c.default.number().min(4).max(8).optional()})});var Q=class{#config;constructor(e){if(!Z.safeParse(e).success)throw Error(`❌ Failed to initiate CAuth. You provided an invalid config!`);this.#config=e}get RoleType(){return null}Guard=e=>this.#config.routeContractor.Guard({config:this.#config,tokens:this.Tokens,roles:e});Routes={Register:()=>this.#config.routeContractor.Register({config:this.#config,tokens:this.Tokens}),Login:()=>this.#config.routeContractor.Login({config:this.#config,tokens:this.Tokens}),Logout:()=>this.#config.routeContractor.Logout({config:this.#config,tokens:this.Tokens}),Refresh:()=>this.#config.routeContractor.Refresh({config:this.#config,tokens:this.Tokens}),ChangePassword:e=>this.#config.routeContractor.ChangePassword({config:this.#config,tokens:this.Tokens,userId:e})};FN={Login:({...e})=>R({config:this.#config,tokens:this.Tokens},e),Register:({...e})=>H({config:this.#config,tokens:this.Tokens},e),Logout:({...e})=>B({config:this.#config,tokens:this.Tokens},e),Refresh:({...e})=>V({config:this.#config,tokens:this.Tokens},e),ChangePassword:({...e})=>L({config:this.#config,tokens:this.Tokens},e),RequestOTPCode:({...e})=>P({config:this.#config,tokens:this.Tokens},e),LoginWithOTP:({...e})=>F({config:this.#config,tokens:this.Tokens},{...e}),VerifyOTP:({...e})=>I({config:this.#config,tokens:this.Tokens},e)};Tokens={GenerateRefreshToken:e=>W({payload:e,config:this.#config}),GenerateAccessToken:e=>U({payload:e,config:this.#config}),GenerateTokenPairs:e=>G({payload:e,config:this.#config}),VerifyRefreshToken:e=>K({token:e,config:this.#config}),VerifyAccessToken:e=>q({token:e,config:this.#config})}};function $(e){return new Q(e)}exports.AccountNotFoundError=g,exports.CAuth=$,exports.CredentialMismatchError=m,exports.DuplicateAccountError=y,exports.InvalidDataError=h,exports.InvalidOTPCode=b,exports.InvalidRefreshTokenError=v,exports.InvalidRoleError=_;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import z$1, { z } from "zod";
|
|
2
|
+
import ms from "ms";
|
|
3
|
+
|
|
4
|
+
//#region src/errors/errors.d.ts
|
|
5
|
+
declare class CError {
|
|
6
|
+
static type: string;
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* @description Error thrown when the credentials provided do not match.
|
|
11
|
+
*/
|
|
12
|
+
declare class CredentialMismatchError extends Error implements CError {
|
|
13
|
+
code: string;
|
|
14
|
+
static type: string;
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @description Error thrown when the data provided is invalid.
|
|
19
|
+
*/
|
|
20
|
+
declare class InvalidDataError extends Error implements CError {
|
|
21
|
+
code: string;
|
|
22
|
+
static type: string;
|
|
23
|
+
constructor(reason: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @description Error thrown when the account is not found.
|
|
27
|
+
*/
|
|
28
|
+
declare class AccountNotFoundError extends Error implements CError {
|
|
29
|
+
code: string;
|
|
30
|
+
static type: string;
|
|
31
|
+
constructor();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @description Error thrown when the role provided is invalid.
|
|
35
|
+
*/
|
|
36
|
+
declare class InvalidRoleError extends Error implements CError {
|
|
37
|
+
code: string;
|
|
38
|
+
static type: string;
|
|
39
|
+
constructor(roles: string[]);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @description Error thrown when an invalid or expired refresh token is provided
|
|
43
|
+
*/
|
|
44
|
+
declare class InvalidRefreshTokenError extends Error implements CError {
|
|
45
|
+
code: string;
|
|
46
|
+
static type: string;
|
|
47
|
+
constructor();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @description Error thrown when trying to create an account that already exists
|
|
51
|
+
*/
|
|
52
|
+
declare class DuplicateAccountError extends Error implements CError {
|
|
53
|
+
code: string;
|
|
54
|
+
static type: string;
|
|
55
|
+
constructor();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @description Error thrown when an invalid or expired OTP is provided
|
|
59
|
+
*/
|
|
60
|
+
declare class InvalidOTPCode extends Error implements CError {
|
|
61
|
+
code: string;
|
|
62
|
+
static type: string;
|
|
63
|
+
constructor();
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/types/auth.t.d.ts
|
|
67
|
+
type Account = {
|
|
68
|
+
id: string;
|
|
69
|
+
phoneNumber: string;
|
|
70
|
+
email: string;
|
|
71
|
+
role: string;
|
|
72
|
+
lastLogin: Date;
|
|
73
|
+
createdAt: Date;
|
|
74
|
+
updatedAt: Date;
|
|
75
|
+
};
|
|
76
|
+
type Tokens = {
|
|
77
|
+
accessToken: string;
|
|
78
|
+
refreshToken: string;
|
|
79
|
+
};
|
|
80
|
+
declare const AuthModelSchema: z$1.ZodObject<{
|
|
81
|
+
id: z$1.ZodString;
|
|
82
|
+
phoneNumber: z$1.ZodString;
|
|
83
|
+
email: z$1.ZodString;
|
|
84
|
+
passwordHash: z$1.ZodOptional<z$1.ZodString>;
|
|
85
|
+
role: z$1.ZodString;
|
|
86
|
+
lastLogin: z$1.ZodDate;
|
|
87
|
+
refreshTokens: z$1.ZodOptional<z$1.ZodArray<z$1.ZodString>>;
|
|
88
|
+
createdAt: z$1.ZodDate;
|
|
89
|
+
updatedAt: z$1.ZodDate;
|
|
90
|
+
}, z$1.z.core.$strip>;
|
|
91
|
+
type AuthModel = z$1.infer<typeof AuthModelSchema>;
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/types/result.t.d.ts
|
|
94
|
+
type FNError = {
|
|
95
|
+
type: string;
|
|
96
|
+
error: Error;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* @description Core Result type.
|
|
100
|
+
* @template T - The type of the value.
|
|
101
|
+
* @template E - The type of the errors, which must extend { type: string; error: Error }.
|
|
102
|
+
*/
|
|
103
|
+
type Result$1<T, E extends FNError = FNError> = {
|
|
104
|
+
success: true;
|
|
105
|
+
value: T;
|
|
106
|
+
} | {
|
|
107
|
+
success: false;
|
|
108
|
+
errors: E[];
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/types/otp-purpose.t.d.ts
|
|
112
|
+
type OtpPurpose = 'LOGIN' | 'RESET_PASSWORD' | 'ACTION';
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/types/database.contract.d.ts
|
|
115
|
+
interface DatabaseContract {
|
|
116
|
+
findAccountById<T = AuthModel>({
|
|
117
|
+
...args
|
|
118
|
+
}: {
|
|
119
|
+
id: string;
|
|
120
|
+
select?: any;
|
|
121
|
+
}): Promise<T | undefined>;
|
|
122
|
+
findAccountWithCredential<T = AuthModel>({
|
|
123
|
+
...args
|
|
124
|
+
}: {
|
|
125
|
+
email?: string | undefined;
|
|
126
|
+
phoneNumber?: string | undefined;
|
|
127
|
+
select?: any;
|
|
128
|
+
}): Promise<T | undefined>;
|
|
129
|
+
createAccount<T = AuthModel>({
|
|
130
|
+
...args
|
|
131
|
+
}: {
|
|
132
|
+
data: any;
|
|
133
|
+
select?: any;
|
|
134
|
+
}): Promise<T>;
|
|
135
|
+
updateAccount<T = AuthModel>({
|
|
136
|
+
...args
|
|
137
|
+
}: {
|
|
138
|
+
id: string;
|
|
139
|
+
data: any;
|
|
140
|
+
select?: any;
|
|
141
|
+
}): Promise<T>;
|
|
142
|
+
updateAccountLogin<T = AuthModel>({
|
|
143
|
+
...args
|
|
144
|
+
}: {
|
|
145
|
+
id: string;
|
|
146
|
+
refreshToken: string;
|
|
147
|
+
select?: any;
|
|
148
|
+
}): Promise<T>;
|
|
149
|
+
removeAndAddRefreshToken({
|
|
150
|
+
...args
|
|
151
|
+
}: {
|
|
152
|
+
id: string;
|
|
153
|
+
refreshToken: string;
|
|
154
|
+
newRefreshToken?: string;
|
|
155
|
+
select?: any;
|
|
156
|
+
}): Promise<any>;
|
|
157
|
+
deleteAccount({
|
|
158
|
+
...args
|
|
159
|
+
}: {
|
|
160
|
+
id: string;
|
|
161
|
+
}): Promise<void>;
|
|
162
|
+
createOTP<T = {
|
|
163
|
+
code: string;
|
|
164
|
+
purpose: string;
|
|
165
|
+
expiresAt: Date;
|
|
166
|
+
}>({
|
|
167
|
+
config
|
|
168
|
+
}: {
|
|
169
|
+
config: CAuthOptions;
|
|
170
|
+
}, {
|
|
171
|
+
...args
|
|
172
|
+
}: {
|
|
173
|
+
id: string;
|
|
174
|
+
purpose: OtpPurpose;
|
|
175
|
+
}): Promise<T>;
|
|
176
|
+
verifyOTP<T = {
|
|
177
|
+
isValid: boolean;
|
|
178
|
+
}>({
|
|
179
|
+
...args
|
|
180
|
+
}: {
|
|
181
|
+
id: string;
|
|
182
|
+
code: string;
|
|
183
|
+
purpose: OtpPurpose;
|
|
184
|
+
}): Promise<T>;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/types/routes.contract.t.d.ts
|
|
188
|
+
type RouteDeps = {
|
|
189
|
+
config: CAuthOptions;
|
|
190
|
+
tokens: _CAuth<any>['Tokens'];
|
|
191
|
+
};
|
|
192
|
+
type AuthGuardDeps = {
|
|
193
|
+
config: CAuthOptions;
|
|
194
|
+
tokens: _CAuth<any>['Tokens'];
|
|
195
|
+
roles?: Array<string>;
|
|
196
|
+
};
|
|
197
|
+
interface RoutesContract {
|
|
198
|
+
Login({
|
|
199
|
+
...config
|
|
200
|
+
}: RouteDeps): any;
|
|
201
|
+
Register({
|
|
202
|
+
...config
|
|
203
|
+
}: RouteDeps): any;
|
|
204
|
+
Logout({
|
|
205
|
+
...config
|
|
206
|
+
}: RouteDeps): any;
|
|
207
|
+
Guard({
|
|
208
|
+
...config
|
|
209
|
+
}: AuthGuardDeps): any;
|
|
210
|
+
Refresh({
|
|
211
|
+
...config
|
|
212
|
+
}: AuthGuardDeps): any;
|
|
213
|
+
ChangePassword({
|
|
214
|
+
...config
|
|
215
|
+
}: RouteDeps & {
|
|
216
|
+
userId: string;
|
|
217
|
+
}): any;
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/types/config.t.d.ts
|
|
221
|
+
declare const CAuthOptionsSchema: z$1.ZodObject<{
|
|
222
|
+
dbContractor: z$1.ZodCustom<DatabaseContract, DatabaseContract>;
|
|
223
|
+
routeContractor: z$1.ZodCustom<RoutesContract, RoutesContract>;
|
|
224
|
+
roles: z$1.ZodArray<z$1.ZodString>;
|
|
225
|
+
jwtConfig: z$1.ZodObject<{
|
|
226
|
+
refreshTokenSecret: z$1.ZodString;
|
|
227
|
+
accessTokenSecret: z$1.ZodString;
|
|
228
|
+
accessTokenLifeSpan: z$1.ZodOptional<z$1.ZodCustom<ms.StringValue, ms.StringValue>>;
|
|
229
|
+
refreshTokenLifeSpan: z$1.ZodOptional<z$1.ZodCustom<ms.StringValue, ms.StringValue>>;
|
|
230
|
+
}, z$1.z.core.$strip>;
|
|
231
|
+
otpConfig: z$1.ZodObject<{
|
|
232
|
+
expiresIn: z$1.ZodOptional<z$1.ZodNumber>;
|
|
233
|
+
length: z$1.ZodOptional<z$1.ZodNumber>;
|
|
234
|
+
}, z$1.z.core.$strip>;
|
|
235
|
+
}, z$1.z.core.$strip>;
|
|
236
|
+
type CAuthOptions = z$1.infer<typeof CAuthOptionsSchema>;
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/types/dto-schemas.t.d.ts
|
|
239
|
+
declare const LoginSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
240
|
+
email: z.ZodEmail;
|
|
241
|
+
phoneNumber: z.ZodOptional<z.ZodNever>;
|
|
242
|
+
password: z.ZodString;
|
|
243
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
244
|
+
phoneNumber: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
245
|
+
email: z.ZodOptional<z.ZodNever>;
|
|
246
|
+
password: z.ZodString;
|
|
247
|
+
}, z.core.$strip>]>;
|
|
248
|
+
type LoginSchemaType = z.infer<typeof LoginSchema>;
|
|
249
|
+
declare const RegisterSchema: z.ZodObject<{
|
|
250
|
+
phoneNumber: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
251
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
252
|
+
role: z.ZodString;
|
|
253
|
+
password: z.ZodString;
|
|
254
|
+
}, z.core.$strip>;
|
|
255
|
+
type RegisterSchemaType = z.infer<typeof RegisterSchema>;
|
|
256
|
+
declare const RefreshTokenSchema: z.ZodObject<{
|
|
257
|
+
refreshToken: z.ZodString;
|
|
258
|
+
}, z.core.$strip>;
|
|
259
|
+
type RefreshTokenSchemaType = z.infer<typeof RefreshTokenSchema>;
|
|
260
|
+
declare const LogoutSchema: z.ZodObject<{
|
|
261
|
+
refreshToken: z.ZodString;
|
|
262
|
+
}, z.core.$strip>;
|
|
263
|
+
type LogoutSchemaType = z.infer<typeof LogoutSchema>;
|
|
264
|
+
declare const ChangePasswordSchema: z.ZodObject<{
|
|
265
|
+
accountId: z.ZodString;
|
|
266
|
+
oldPassword: z.ZodString;
|
|
267
|
+
newPassword: z.ZodString;
|
|
268
|
+
}, z.core.$strip>;
|
|
269
|
+
type ChangePasswordSchemaType = z.infer<typeof ChangePasswordSchema>;
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/cauth.d.ts
|
|
272
|
+
declare class _CAuth<T extends string[]> {
|
|
273
|
+
#private;
|
|
274
|
+
constructor(config: Omit<CAuthOptions, 'roles'> & {
|
|
275
|
+
roles: T;
|
|
276
|
+
});
|
|
277
|
+
get RoleType(): T[number];
|
|
278
|
+
/**
|
|
279
|
+
* @description Authentication Guard Middleware. Include 'roles' for a custom auth guard.
|
|
280
|
+
*
|
|
281
|
+
* If 'roles' is empty it allows all authenticated users, without respecting specific role
|
|
282
|
+
*
|
|
283
|
+
* @default undefined
|
|
284
|
+
*/
|
|
285
|
+
Guard: (roles?: Array<T[number]>) => any;
|
|
286
|
+
Routes: {
|
|
287
|
+
Register: () => any;
|
|
288
|
+
Login: () => any;
|
|
289
|
+
Logout: () => any;
|
|
290
|
+
Refresh: () => any;
|
|
291
|
+
ChangePassword: (userId: string) => any;
|
|
292
|
+
};
|
|
293
|
+
FN: {
|
|
294
|
+
Login: ({
|
|
295
|
+
...args
|
|
296
|
+
}: LoginSchemaType) => Promise<Result$1<{
|
|
297
|
+
account: Account;
|
|
298
|
+
tokens: Tokens;
|
|
299
|
+
}>>;
|
|
300
|
+
Register: ({
|
|
301
|
+
...args
|
|
302
|
+
}: RegisterSchemaType) => Promise<Result<{
|
|
303
|
+
account: Account;
|
|
304
|
+
tokens: Tokens;
|
|
305
|
+
}>>;
|
|
306
|
+
Logout: ({
|
|
307
|
+
...args
|
|
308
|
+
}: LogoutSchemaType) => Promise<Result<any>>;
|
|
309
|
+
Refresh: ({
|
|
310
|
+
...args
|
|
311
|
+
}: RefreshTokenSchemaType) => Promise<Result$1<{
|
|
312
|
+
account: Account;
|
|
313
|
+
tokens: Tokens;
|
|
314
|
+
}>>;
|
|
315
|
+
ChangePassword: ({
|
|
316
|
+
...args
|
|
317
|
+
}: ChangePasswordSchemaType) => Promise<Result<unknown>>;
|
|
318
|
+
RequestOTPCode: ({
|
|
319
|
+
...args
|
|
320
|
+
}: Omit<LoginSchemaType, "password"> & {
|
|
321
|
+
password?: string;
|
|
322
|
+
usePassword?: boolean;
|
|
323
|
+
otpPurpose: OtpPurpose;
|
|
324
|
+
}) => Promise<Result<{
|
|
325
|
+
id: string;
|
|
326
|
+
code: string;
|
|
327
|
+
}>>;
|
|
328
|
+
LoginWithOTP: ({
|
|
329
|
+
...args
|
|
330
|
+
}: Omit<LoginSchemaType, "password"> & {
|
|
331
|
+
code: string;
|
|
332
|
+
}) => Promise<Result<{
|
|
333
|
+
account: Account;
|
|
334
|
+
tokens: Tokens;
|
|
335
|
+
}>>;
|
|
336
|
+
VerifyOTP: ({
|
|
337
|
+
...args
|
|
338
|
+
}: {
|
|
339
|
+
id: string;
|
|
340
|
+
code: string;
|
|
341
|
+
otpPurpose: OtpPurpose;
|
|
342
|
+
}) => Promise<{
|
|
343
|
+
isValid: boolean;
|
|
344
|
+
}>;
|
|
345
|
+
};
|
|
346
|
+
Tokens: {
|
|
347
|
+
GenerateRefreshToken: (payload: any) => Promise<string>;
|
|
348
|
+
GenerateAccessToken: (payload: any) => Promise<string>;
|
|
349
|
+
GenerateTokenPairs: (payload: any) => Promise<{
|
|
350
|
+
accessToken: string;
|
|
351
|
+
refreshToken: string;
|
|
352
|
+
}>;
|
|
353
|
+
VerifyRefreshToken: <T_1>(token: any) => Promise<T_1 | null>;
|
|
354
|
+
VerifyAccessToken: <T_1>(token: any) => Promise<T_1 | null>;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
declare function CAuth<const T extends string[]>(options: Omit<CAuthOptions, 'roles'> & {
|
|
358
|
+
roles: T;
|
|
359
|
+
}): _CAuth<T>;
|
|
360
|
+
//#endregion
|
|
361
|
+
export { AccountNotFoundError, CAuth, type CAuthOptions, CredentialMismatchError, type DatabaseContract, DuplicateAccountError, InvalidDataError, InvalidOTPCode, InvalidRefreshTokenError, InvalidRoleError, type RoutesContract };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cauth/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"types": "./dist/index.d.cts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsdown",
|
|
10
|
+
"patch": "npm version patch",
|
|
11
|
+
"minor": "npm version minor",
|
|
12
|
+
"major": "npm version major",
|
|
13
|
+
"publish": "npm publish"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"package.json"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"authentication",
|
|
22
|
+
"node-auth",
|
|
23
|
+
"express auth",
|
|
24
|
+
"easy-auth"
|
|
25
|
+
],
|
|
26
|
+
"author": "Jonace Mpelule <jonacempelule123@gmail.com> (https://github.com/jonace-mpelule)",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"packageManager": "pnpm@10.13.1",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/bcrypt": "^6.0.0",
|
|
31
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
32
|
+
"@types/ms": "^2.1.0",
|
|
33
|
+
"tsdown": "^0.15.6",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"bcrypt": "^6.0.0",
|
|
38
|
+
"jsonwebtoken": "^9.0.2",
|
|
39
|
+
"libphonenumber-js": "^1.12.23",
|
|
40
|
+
"ms": "^2.1.3",
|
|
41
|
+
"zod": "^4.1.12"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
}
|
|
46
|
+
}
|