pico-auth 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,18 @@
1
+ declare module "core/auth" {
2
+ /**
3
+ * When mfaToken is provided
4
+ */
5
+ export const authenticate: (login: string, password: string, mfaToken: string, impersonateEntity: string, userProvider: any, impersonateProvider: any) => Promise<any>;
6
+ /**
7
+ * Will prepare user for MFA activation. Next step is to call verify with token generated in MFA app by the user.
8
+ */
9
+ export const mfaRegister: (appName: string, login: string, userProvider: any) => Promise<unknown>;
10
+ /**
11
+ * Will return true and fully initialize MFA for user when token verification was ok. Otherwise will result false;
12
+ */
13
+ export const mfaVerify: (login: string, mfaToken: string, userProvider: any) => Promise<boolean>;
14
+ export const mfaEnabled: (login: string, userProvider: any) => Promise<any>;
15
+ }
16
+ declare module "pico-auth" {
17
+ export { mfaRegister, mfaVerify, mfaEnabled, authenticate } from "core/auth";
18
+ }
@@ -0,0 +1,141 @@
1
+ const speakeasy = require('speakeasy');
2
+ const qrcode = require('qrcode');
3
+ const md5 = require("md5");
4
+ const jwt = require('jsonwebtoken');
5
+ /**
6
+ * When mfaToken is provided
7
+ */
8
+ const authenticate = async (login, password, mfaToken, impersonateEntity, userProvider, impersonateProvider) => {
9
+ var _a, _b, _c;
10
+ let user = await userProvider.getUser(login);
11
+ if ((_a = user.mfa) === null || _a === void 0 ? void 0 : _a.enabled) {
12
+ // Validate the token against the user's saved secret
13
+ const validated = speakeasy.totp.verify({
14
+ secret: (_c = (_b = user.mfa) === null || _b === void 0 ? void 0 : _b.secret) === null || _c === void 0 ? void 0 : _c.actual,
15
+ encoding: 'base32',
16
+ mfaToken,
17
+ window: 1, // Adjust window size if tokens have a margin of error
18
+ });
19
+ if (!validated)
20
+ throw new Error(`Failed authentication attempt ${login}`);
21
+ }
22
+ if (md5(password || '') == user.password) {
23
+ // check if impersonate mode - this is not yet implemented fully just copy pasta from GRM project
24
+ const target = impersonateEntity; // either target user login or @organizationId
25
+ const originalUser = user;
26
+ if (target) {
27
+ // impersonate flow
28
+ let mayImpersonate = false;
29
+ // check mode - when starts with @ we try to impersonate only to organization, otherwise we impersonate to another user
30
+ if (target.startsWith("@")) {
31
+ // only organization impersonation
32
+ // check requesting user has global admin
33
+ // mayImpersonate = mayImpersonate || user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.GRM_ADMIN);
34
+ mayImpersonate = mayImpersonate || impersonateProvider.canImpersonate(user, target);
35
+ // mayImpersonate = true;
36
+ // todo check org exists
37
+ if (!mayImpersonate) {
38
+ throw new Error(`Failed impersonate attempt. From: ${originalUser.id} into ${target}`);
39
+ }
40
+ // switch original user organization_id
41
+ impersonateProvider.impersonate(user, target);
42
+ // await userManager.impersonateOrganization(user, target.substring(1).trim());
43
+ // const organization = await commons.dbApi.adminApi.organization();
44
+ // user.organization_id = parseInt(target.substring(1).trim()); // skip "@" at the beginning
45
+ // user.organization = organization;
46
+ }
47
+ else {
48
+ // full user impersonation
49
+ // load target user
50
+ const targetUser = await userProvider.getUser(target);
51
+ // check requesting user has target user's org admin role
52
+ // mayImpersonate = mayImpersonate || (user.organization_id == targetUser.organization_id && user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.ORG_ADMIN))
53
+ // check requesting user has global admin
54
+ // mayImpersonate = mayImpersonate || user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.GRM_ADMIN);
55
+ mayImpersonate = mayImpersonate || impersonateProvider.canImpersonate(user, target);
56
+ if (!mayImpersonate) {
57
+ throw new Error(`Failed impersonate attempt. From: ${originalUser.id} into ${target}`);
58
+ }
59
+ // allowed to impersonate so "switch" user to target user
60
+ user = targetUser;
61
+ }
62
+ console.info(`Impersonate success. From: ${originalUser.login} into ${target}`);
63
+ }
64
+ let jwtSecretKey = process.env.JWT_SECRET_KEY;
65
+ let data = {
66
+ time: Date.now(),
67
+ user: user
68
+ };
69
+ const token = jwt.sign(data, jwtSecretKey, { expiresIn: process.env.JWT_EXPIRY_TIME });
70
+ console.log(`Successful login: ${user.id}`);
71
+ return token;
72
+ }
73
+ else {
74
+ throw new Error(`Failed authentication attempt ${login}`);
75
+ }
76
+ };
77
+ /**
78
+ * Will prepare user for MFA activation. Next step is to call verify with token generated in MFA app by the user.
79
+ */
80
+ const mfaRegister = async (appName, login, userProvider) => {
81
+ return new Promise(async (resolve, _reject) => {
82
+ let user = await userProvider.getUser(login);
83
+ const secret = speakeasy.generateSecret({
84
+ name: `${appName} (${login})`,
85
+ });
86
+ if (!user.mfa)
87
+ user.mfa = {
88
+ secret: {
89
+ temp: undefined,
90
+ actual: undefined,
91
+ enabled: true
92
+ }
93
+ };
94
+ user.mfa.secret.temp = secret.base32;
95
+ user.mfa.secret.actual = undefined;
96
+ await userProvider.putUser(user);
97
+ qrcode.toDataURL(secret.otpauth_url, (err, data) => {
98
+ if (err) {
99
+ throw new Error('Error generating QR code');
100
+ }
101
+ else {
102
+ // Send the QR code URL and the secret
103
+ resolve({
104
+ qr_code: data,
105
+ secret: secret.base32
106
+ });
107
+ }
108
+ });
109
+ });
110
+ };
111
+ /**
112
+ * Will return true and fully initialize MFA for user when token verification was ok. Otherwise will result false;
113
+ */
114
+ const mfaVerify = async (login, mfaToken, userProvider) => {
115
+ var _a, _b, _c, _d;
116
+ const token = mfaToken;
117
+ // load user
118
+ let user = await userProvider.getUser(login);
119
+ // Verify the token using the saved secret
120
+ const verified = speakeasy.totp.verify({
121
+ secret: (_b = (_a = user.mfa) === null || _a === void 0 ? void 0 : _a.secret) === null || _b === void 0 ? void 0 : _b.temp,
122
+ encoding: 'base32',
123
+ token,
124
+ });
125
+ if (verified) {
126
+ user.mfa.secret.actual = (_d = (_c = user.mfa) === null || _c === void 0 ? void 0 : _c.secret) === null || _d === void 0 ? void 0 : _d.temp;
127
+ await userProvider.putUser(user);
128
+ return true;
129
+ }
130
+ else {
131
+ console.log(`Failed mfa verification for ${login}`);
132
+ return false;
133
+ }
134
+ };
135
+ const mfaEnabled = async (login, userProvider) => {
136
+ var _a;
137
+ let user = await userProvider.getUser(login);
138
+ return (_a = user.mfa) === null || _a === void 0 ? void 0 : _a.enabled;
139
+ };
140
+
141
+ export { authenticate, mfaEnabled, mfaRegister, mfaVerify };
@@ -0,0 +1 @@
1
+ const speakeasy=require("speakeasy"),qrcode=require("qrcode"),md5=require("md5"),jwt=require("jsonwebtoken"),authenticate=async(e,t,a,r,o,i)=>{var n,s,c;let l=await o.getUser(e);if(null===(n=l.mfa)||void 0===n?void 0:n.enabled){if(!speakeasy.totp.verify({secret:null===(c=null===(s=l.mfa)||void 0===s?void 0:s.secret)||void 0===c?void 0:c.actual,encoding:"base32",mfaToken:a,window:1}))throw new Error(`Failed authentication attempt ${e}`)}if(md5(t||"")==l.password){const e=r,t=l;if(e){let a=!1;if(e.startsWith("@")){if(a=a||i.canImpersonate(l,e),!a)throw new Error(`Failed impersonate attempt. From: ${t.id} into ${e}`);i.impersonate(l,e)}else{const r=await o.getUser(e);if(a=a||i.canImpersonate(l,e),!a)throw new Error(`Failed impersonate attempt. From: ${t.id} into ${e}`);l=r}console.info(`Impersonate success. From: ${t.login} into ${e}`)}let a=process.env.JWT_SECRET_KEY,n={time:Date.now(),user:l};const s=jwt.sign(n,a,{expiresIn:process.env.JWT_EXPIRY_TIME});return console.log(`Successful login: ${l.id}`),s}throw new Error(`Failed authentication attempt ${e}`)},mfaRegister=async(e,t,a)=>new Promise((async(r,o)=>{let i=await a.getUser(t);const n=speakeasy.generateSecret({name:`${e} (${t})`});i.mfa||(i.mfa={secret:{temp:void 0,actual:void 0,enabled:!0}}),i.mfa.secret.temp=n.base32,i.mfa.secret.actual=void 0,await a.putUser(i),qrcode.toDataURL(n.otpauth_url,((e,t)=>{if(e)throw new Error("Error generating QR code");r({qr_code:t,secret:n.base32})}))})),mfaVerify=async(e,t,a)=>{var r,o,i,n;const s=t;let c=await a.getUser(e);return speakeasy.totp.verify({secret:null===(o=null===(r=c.mfa)||void 0===r?void 0:r.secret)||void 0===o?void 0:o.temp,encoding:"base32",token:s})?(c.mfa.secret.actual=null===(n=null===(i=c.mfa)||void 0===i?void 0:i.secret)||void 0===n?void 0:n.temp,await a.putUser(c),!0):(console.log(`Failed mfa verification for ${e}`),!1)},mfaEnabled=async(e,t)=>{var a;return null===(a=(await t.getUser(e)).mfa)||void 0===a?void 0:a.enabled};export{authenticate,mfaEnabled,mfaRegister,mfaVerify};
@@ -0,0 +1,154 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.picoAuth = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ const speakeasy = require('speakeasy');
8
+ const qrcode = require('qrcode');
9
+ const md5 = require("md5");
10
+ const jwt = require('jsonwebtoken');
11
+ /**
12
+ * When mfaToken is provided
13
+ */
14
+ const authenticate = async (login, password, mfaToken, impersonateEntity, userProvider, impersonateProvider) => {
15
+ var _a, _b, _c;
16
+ let user = await userProvider.getUser(login);
17
+ if ((_a = user.mfa) === null || _a === void 0 ? void 0 : _a.enabled) {
18
+ // Validate the token against the user's saved secret
19
+ const validated = speakeasy.totp.verify({
20
+ secret: (_c = (_b = user.mfa) === null || _b === void 0 ? void 0 : _b.secret) === null || _c === void 0 ? void 0 : _c.actual,
21
+ encoding: 'base32',
22
+ mfaToken,
23
+ window: 1, // Adjust window size if tokens have a margin of error
24
+ });
25
+ if (!validated)
26
+ throw new Error(`Failed authentication attempt ${login}`);
27
+ }
28
+ if (md5(password || '') == user.password) {
29
+ // check if impersonate mode - this is not yet implemented fully just copy pasta from GRM project
30
+ const target = impersonateEntity; // either target user login or @organizationId
31
+ const originalUser = user;
32
+ if (target) {
33
+ // impersonate flow
34
+ let mayImpersonate = false;
35
+ // check mode - when starts with @ we try to impersonate only to organization, otherwise we impersonate to another user
36
+ if (target.startsWith("@")) {
37
+ // only organization impersonation
38
+ // check requesting user has global admin
39
+ // mayImpersonate = mayImpersonate || user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.GRM_ADMIN);
40
+ mayImpersonate = mayImpersonate || impersonateProvider.canImpersonate(user, target);
41
+ // mayImpersonate = true;
42
+ // todo check org exists
43
+ if (!mayImpersonate) {
44
+ throw new Error(`Failed impersonate attempt. From: ${originalUser.id} into ${target}`);
45
+ }
46
+ // switch original user organization_id
47
+ impersonateProvider.impersonate(user, target);
48
+ // await userManager.impersonateOrganization(user, target.substring(1).trim());
49
+ // const organization = await commons.dbApi.adminApi.organization();
50
+ // user.organization_id = parseInt(target.substring(1).trim()); // skip "@" at the beginning
51
+ // user.organization = organization;
52
+ }
53
+ else {
54
+ // full user impersonation
55
+ // load target user
56
+ const targetUser = await userProvider.getUser(target);
57
+ // check requesting user has target user's org admin role
58
+ // mayImpersonate = mayImpersonate || (user.organization_id == targetUser.organization_id && user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.ORG_ADMIN))
59
+ // check requesting user has global admin
60
+ // mayImpersonate = mayImpersonate || user.roles.map(role=>role.toUpperCase()).includes(UserManager.CONST.ROLES.GRM_ADMIN);
61
+ mayImpersonate = mayImpersonate || impersonateProvider.canImpersonate(user, target);
62
+ if (!mayImpersonate) {
63
+ throw new Error(`Failed impersonate attempt. From: ${originalUser.id} into ${target}`);
64
+ }
65
+ // allowed to impersonate so "switch" user to target user
66
+ user = targetUser;
67
+ }
68
+ console.info(`Impersonate success. From: ${originalUser.login} into ${target}`);
69
+ }
70
+ let jwtSecretKey = process.env.JWT_SECRET_KEY;
71
+ let data = {
72
+ time: Date.now(),
73
+ user: user
74
+ };
75
+ const token = jwt.sign(data, jwtSecretKey, { expiresIn: process.env.JWT_EXPIRY_TIME });
76
+ console.log(`Successful login: ${user.id}`);
77
+ return token;
78
+ }
79
+ else {
80
+ throw new Error(`Failed authentication attempt ${login}`);
81
+ }
82
+ };
83
+ /**
84
+ * Will prepare user for MFA activation. Next step is to call verify with token generated in MFA app by the user.
85
+ */
86
+ const mfaRegister = async (appName, login, userProvider) => {
87
+ return new Promise(async (resolve, _reject) => {
88
+ let user = await userProvider.getUser(login);
89
+ const secret = speakeasy.generateSecret({
90
+ name: `${appName} (${login})`,
91
+ });
92
+ if (!user.mfa)
93
+ user.mfa = {
94
+ secret: {
95
+ temp: undefined,
96
+ actual: undefined,
97
+ enabled: true
98
+ }
99
+ };
100
+ user.mfa.secret.temp = secret.base32;
101
+ user.mfa.secret.actual = undefined;
102
+ await userProvider.putUser(user);
103
+ qrcode.toDataURL(secret.otpauth_url, (err, data) => {
104
+ if (err) {
105
+ throw new Error('Error generating QR code');
106
+ }
107
+ else {
108
+ // Send the QR code URL and the secret
109
+ resolve({
110
+ qr_code: data,
111
+ secret: secret.base32
112
+ });
113
+ }
114
+ });
115
+ });
116
+ };
117
+ /**
118
+ * Will return true and fully initialize MFA for user when token verification was ok. Otherwise will result false;
119
+ */
120
+ const mfaVerify = async (login, mfaToken, userProvider) => {
121
+ var _a, _b, _c, _d;
122
+ const token = mfaToken;
123
+ // load user
124
+ let user = await userProvider.getUser(login);
125
+ // Verify the token using the saved secret
126
+ const verified = speakeasy.totp.verify({
127
+ secret: (_b = (_a = user.mfa) === null || _a === void 0 ? void 0 : _a.secret) === null || _b === void 0 ? void 0 : _b.temp,
128
+ encoding: 'base32',
129
+ token,
130
+ });
131
+ if (verified) {
132
+ user.mfa.secret.actual = (_d = (_c = user.mfa) === null || _c === void 0 ? void 0 : _c.secret) === null || _d === void 0 ? void 0 : _d.temp;
133
+ await userProvider.putUser(user);
134
+ return true;
135
+ }
136
+ else {
137
+ console.log(`Failed mfa verification for ${login}`);
138
+ return false;
139
+ }
140
+ };
141
+ const mfaEnabled = async (login, userProvider) => {
142
+ var _a;
143
+ let user = await userProvider.getUser(login);
144
+ return (_a = user.mfa) === null || _a === void 0 ? void 0 : _a.enabled;
145
+ };
146
+
147
+ exports.authenticate = authenticate;
148
+ exports.mfaEnabled = mfaEnabled;
149
+ exports.mfaRegister = mfaRegister;
150
+ exports.mfaVerify = mfaVerify;
151
+
152
+ Object.defineProperty(exports, '__esModule', { value: true });
153
+
154
+ }));
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).picoAuth={})}(this,(function(e){"use strict";const t=require("speakeasy"),o=require("qrcode"),a=require("md5"),r=require("jsonwebtoken");e.authenticate=async(e,o,i,n,s,c)=>{var l,d,f;let u=await s.getUser(e);if(null===(l=u.mfa)||void 0===l?void 0:l.enabled){if(!t.totp.verify({secret:null===(f=null===(d=u.mfa)||void 0===d?void 0:d.secret)||void 0===f?void 0:f.actual,encoding:"base32",mfaToken:i,window:1}))throw new Error(`Failed authentication attempt ${e}`)}if(a(o||"")==u.password){const e=n,t=u;if(e){let o=!1;if(e.startsWith("@")){if(o=o||c.canImpersonate(u,e),!o)throw new Error(`Failed impersonate attempt. From: ${t.id} into ${e}`);c.impersonate(u,e)}else{const a=await s.getUser(e);if(o=o||c.canImpersonate(u,e),!o)throw new Error(`Failed impersonate attempt. From: ${t.id} into ${e}`);u=a}console.info(`Impersonate success. From: ${t.login} into ${e}`)}let o=process.env.JWT_SECRET_KEY,a={time:Date.now(),user:u};const i=r.sign(a,o,{expiresIn:process.env.JWT_EXPIRY_TIME});return console.log(`Successful login: ${u.id}`),i}throw new Error(`Failed authentication attempt ${e}`)},e.mfaEnabled=async(e,t)=>{var o;return null===(o=(await t.getUser(e)).mfa)||void 0===o?void 0:o.enabled},e.mfaRegister=async(e,a,r)=>new Promise((async(i,n)=>{let s=await r.getUser(a);const c=t.generateSecret({name:`${e} (${a})`});s.mfa||(s.mfa={secret:{temp:void 0,actual:void 0,enabled:!0}}),s.mfa.secret.temp=c.base32,s.mfa.secret.actual=void 0,await r.putUser(s),o.toDataURL(c.otpauth_url,((e,t)=>{if(e)throw new Error("Error generating QR code");i({qr_code:t,secret:c.base32})}))})),e.mfaVerify=async(e,o,a)=>{var r,i,n,s;const c=o;let l=await a.getUser(e);return t.totp.verify({secret:null===(i=null===(r=l.mfa)||void 0===r?void 0:r.secret)||void 0===i?void 0:i.temp,encoding:"base32",token:c})?(l.mfa.secret.actual=null===(s=null===(n=l.mfa)||void 0===n?void 0:n.secret)||void 0===s?void 0:s.temp,await a.putUser(l),!0):(console.log(`Failed mfa verification for ${e}`),!1)},Object.defineProperty(e,"__esModule",{value:!0})}));
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "pico-auth",
3
+ "version": "0.0.2",
4
+ "description": "Minimal auth with user/pass, impersonation and mfa authentication",
5
+ "main": "dist/pico-auth.umd.js",
6
+ "types": "dist/pico-auth.d.ts",
7
+ "module": "dist/pico-auth.esm.min.js",
8
+ "homepage": "https://github.com/alkeicam/pico-auth",
9
+ "bugs": {
10
+ "url": "https://github.com/alkeicam/pico-auth/issues"
11
+ },
12
+ "keywords": [
13
+ "impersonate",
14
+ "auth",
15
+ "mfa"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "require": {
20
+ "types": "./dist/pico-auth.d.ts",
21
+ "default": "./dist/pico-auth.umd.min.js"
22
+ },
23
+ "import": {
24
+ "types": "./dist/pico-auth.d.ts",
25
+ "default": "./dist/pico-auth.esm.min.js"
26
+ }
27
+ }
28
+ },
29
+ "scripts": {
30
+ "clean": "rimraf dist/*",
31
+ "docs": "typedoc --entryPointStrategy expand ./src && touch docs/.nojekyll",
32
+ "build": "date && npm run clean && npm run build:js -s && npm run build:minjs -s && npm run build:types -s",
33
+ "build:js": "rollup -c rollup.config.js",
34
+ "build:minjs": "npm run build:minjs:esm -s && npm run build:minjs:umd -s",
35
+ "build:minjs:umd": "terser dist/pico-auth.umd.js --compress --mangle > dist/pico-auth.umd.min.js",
36
+ "build:minjs:esm": "terser dist/pico-auth.esm.js --compress --mangle > dist/pico-auth.esm.min.js",
37
+ "build:types": "tsc -t esnext --moduleResolution node -d --emitDeclarationOnly --outFile dist/pico-auth.d.ts src/pico-auth.ts",
38
+ "test": "env TS_NODE_PROJECT=\"tsconfig-test.json\" mocha -r ts-node/register --require source-map-support/register --recursive **/test/**/*.test.ts",
39
+ "coverage": "nyc --reporter html --reporter text npm test",
40
+ "release-minor": "npm install && npm update && npm run build && npm run coverage && npm version minor && git push origin && git push origin --tags && npm publish"
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "author": "Al Keicam",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "md5": "^2.3.0",
49
+ "speakeasy": "2.0.0",
50
+ "qrcode": "1.5.4",
51
+ "jsonwebtoken": "^8.5.1"
52
+ },
53
+ "devDependencies": {
54
+ "@rollup/plugin-typescript": "^6.1.0",
55
+ "@rollup/plugin-node-resolve": "15.2.3",
56
+ "@rollup/plugin-commonjs": "28.0.1",
57
+ "@types/chai": "4.2.14",
58
+ "@types/chai-as-promised": "7.1.3",
59
+ "@types/mocha": "8.0.3",
60
+ "@types/node": "20.5.0",
61
+ "@types/sinon": "9.0.8",
62
+ "@typescript-eslint/eslint-plugin": "4.6.1",
63
+ "@typescript-eslint/parser": "4.6.1",
64
+ "chai": "^3.5.0",
65
+ "chai-as-promised": "^6.0.0",
66
+ "eslint": "7.12.1",
67
+ "mocha": "^8.4.0",
68
+ "nyc": "14.1.x",
69
+ "prettier": "^2.8.8",
70
+ "pretty-quick": "^3.1.3",
71
+ "rollup": "^2.79.1",
72
+ "sinon": "^9.2.4",
73
+ "terser": "^5.19.2",
74
+ "ts-node": "10.8.2",
75
+ "tslib": "^2.6.2",
76
+ "typedoc": "^0.25.1",
77
+ "typescript": "^5.1.6",
78
+ "source-map-support": "^0.5.21",
79
+ "rimraf": "^5.0.5",
80
+ "shell-exec": "1.1.2"
81
+ },
82
+ "nyc": {
83
+ "check-coverage": true,
84
+ "branches": 100,
85
+ "lines": 100,
86
+ "functions": 100,
87
+ "statements": 100,
88
+ "extension": [
89
+ ".ts",
90
+ ".tsx"
91
+ ],
92
+ "exclude": [
93
+ "**/*.d.ts",
94
+ "**/*.js",
95
+ "**/*.notest.ts",
96
+ "**/*.mock.ts",
97
+ "**/*.test.ts"
98
+ ],
99
+ "all": true
100
+ }
101
+ }
package/readme.MD ADDED
@@ -0,0 +1,3 @@
1
+
2
+
3
+ # Pico Auth