@fullstackunicorn/auth 1.0.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.
package/index.dep.js ADDED
File without changes
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import '@fullstackunicorn/initialize'
2
+ export { auth } from './modules/auth.js'
@@ -0,0 +1,134 @@
1
+ import '@fullstackunicorn/initialize'
2
+ import { fsa, axios, jwt, pem } from '#@/index.dep.js'
3
+
4
+ class Auth {
5
+
6
+ constructor() {
7
+
8
+ this.isRetry = false
9
+
10
+ this.paths = {}
11
+ this.paths.pem = './index.crt'
12
+
13
+ this.vars = {}
14
+ this.vars.realm = process.env.AUTH_REALM
15
+ this.vars.client = process.env.AUTH_CLIENT
16
+
17
+ this.url = {}
18
+ this.url.verify = process.env.URL_VERIFY
19
+ this.url.certificate = process.env.URL_CERTIFICATE
20
+ this.url.issuer = process.env.URL_ISSUER
21
+
22
+ }
23
+
24
+ user = async (authToken, method) => {
25
+ this.isRetry = false
26
+ try {
27
+ const token = this.checkToken(authToken)
28
+ if (!token) return {}
29
+ const user = await this.verifyToken(token, method)
30
+ if (!user?.id) return {}
31
+ return user
32
+ } catch { return {} }
33
+ }
34
+
35
+ isOffline = method => method === 'GET'
36
+
37
+ verifyOffline = async (token, pemKey, options = {}) => {
38
+ try {
39
+ const decoded = jwt.verify(token, pemKey, options)
40
+ return { content: decoded }
41
+ } catch { return {} }
42
+ }
43
+
44
+ verifyOnline = async token => {
45
+ try {
46
+ const res = await axios.post(this.url.verify, { token })
47
+ if (!res?.data?.verified) return {}
48
+ const decoded = jwt.decode(token)
49
+ if (!decoded) return {}
50
+ return { content: decoded }
51
+ } catch { return {} }
52
+ }
53
+
54
+ isEligible = (content = {}) => {
55
+ if (!content?.sub || !content?.exp) return false
56
+ if (content.iss !== this.url.issuer) return false
57
+ if (!content?.aud) return false
58
+ const aud = F3.isArr(content.aud) ? content.aud : [content.aud]
59
+ if (!aud.includes(this.vars.client)) return false
60
+ return true
61
+ }
62
+
63
+ verifyToken = async (token, method) => {
64
+
65
+ let result = {}
66
+
67
+ try {
68
+
69
+ const isOffline = this.isOffline(method)
70
+ if (!isOffline) result = await this.verifyOnline(token)
71
+ else result = await this.verifyOffline(token, await this.getPem(token))
72
+ if (!result?.content?.exp && isOffline) return await this.retryVerifyToken(token, method)
73
+ if (!result?.content?.exp && !isOffline) return {}
74
+ if (this.isTokenExpired(result.content.exp)) return {}
75
+ if (!this.isEligible(result.content)) return {}
76
+ this.isRetry = false
77
+ return { id: result.content.sub, token }
78
+
79
+ } catch { F3.catch.throw('verify-token-failed') }
80
+
81
+ }
82
+
83
+ retryVerifyToken = async (token, method) => {
84
+
85
+ if (this.isRetry) return {}
86
+ this.isRetry = true
87
+
88
+ try {
89
+ await this.updatePem()
90
+ return await this.verifyToken(token, method)
91
+ } catch { F3.catch.throw('retry-verify-token-failed') }
92
+ }
93
+
94
+ isTokenExpired = expiryDate => Date.now() > expiryDate * 1000
95
+
96
+ checkToken = authToken => {
97
+ const isBearer = F3.isStr(authToken) && authToken.startsWith('Bearer ')
98
+ return isBearer ? authToken.replace('Bearer ', '') : false
99
+ }
100
+
101
+ fetchPem = async () => {
102
+ try { return await axios.post(this.url.certificate, { realm: this.vars.realm }) }
103
+ catch { return null }
104
+ }
105
+
106
+ initPem = async () => {
107
+ try { if (!(await fsa.exists(this.paths.pem))) await this.updatePem() }
108
+ catch { F3.catch.throw('init-pem-failed') }
109
+ }
110
+
111
+ updatePem = async () => {
112
+ try {
113
+ const certificate = {}
114
+ const pemObject = await this.fetchPem()
115
+ if (!pemObject?.data?.keys) F3.throw('certificate-fetch-failed')
116
+ pemObject.data.keys.forEach(key => certificate[key.kid] = pem(key))
117
+ await fsa.writeJSON(this.paths.pem, certificate)
118
+ return true
119
+ } catch { F3.catch.throw('update-pem-failed') }
120
+ }
121
+
122
+ getPem = async token => {
123
+ try {
124
+ const file = await fsa.readFile(this.paths.pem)
125
+ const pemObject = JSON.parse(String(file))
126
+ const jwtDecoded = jwt.decode(token, { complete: true })
127
+ return pemObject[jwtDecoded?.header?.kid]
128
+ } catch { F3.catch.throw('get-pem-failed') }
129
+ }
130
+ }
131
+
132
+ const auth = new Auth()
133
+ await auth.initPem()
134
+ export { auth }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "name": "@fullstackunicorn/auth",
4
+ "author": "lucanigido (https://fullstackunicorn.dev/author/lucanigido)",
5
+ "description": "A component to implement OAuth in your project",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "private": false,
9
+ "readme": "package.readme.md",
10
+ "main": "index.js",
11
+ "files": [
12
+ "index.js",
13
+ "index.dep.js",
14
+ "modules/"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://gitlab.com/fullstackunicorn/auth.git"
19
+ },
20
+ "keywords": [
21
+ "authentication",
22
+ "OAuth",
23
+ "react-native",
24
+ "react",
25
+ "fullstackunicorn"
26
+ ],
27
+ "imports": {
28
+ "#@": "./",
29
+ "#@/*": "./*"
30
+ },
31
+ "scripts": {
32
+ "test": "exit 1",
33
+ "run": "chmod +x ./config.run.sh && ./config.run.sh"
34
+ },
35
+ "dependencies": {
36
+ "@fullstackunicorn/initialize": "^1.0.7"
37
+ },
38
+ "devDependencies": {
39
+ "@fullstackunicorn/cli": "^1.0.3",
40
+ "@fullstackunicorn/reduce": "^1.0.19"
41
+ }
42
+ }