@things-factory/auth-base 8.0.0-beta.8 → 8.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/client/actions/auth.ts +24 -0
- package/client/auth.ts +272 -0
- package/client/bootstrap.ts +47 -0
- package/client/directive/privileged.ts +28 -0
- package/client/index.ts +3 -0
- package/client/profiled.ts +83 -0
- package/client/reducers/auth.ts +31 -0
- package/dist-client/index.d.ts +0 -1
- package/dist-client/index.js +0 -1
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/constants/error-code.d.ts +0 -2
- package/dist-server/constants/error-code.js +1 -3
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/controllers/change-pwd.js +2 -2
- package/dist-server/controllers/change-pwd.js.map +1 -1
- package/dist-server/controllers/delete-user.js +12 -13
- package/dist-server/controllers/delete-user.js.map +1 -1
- package/dist-server/controllers/invitation.d.ts +1 -2
- package/dist-server/controllers/invitation.js +5 -30
- package/dist-server/controllers/invitation.js.map +1 -1
- package/dist-server/controllers/profile.d.ts +3 -4
- package/dist-server/controllers/profile.js +2 -20
- package/dist-server/controllers/profile.js.map +1 -1
- package/dist-server/controllers/signin.d.ts +1 -4
- package/dist-server/controllers/signin.js +1 -17
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/controllers/signup.js +4 -13
- package/dist-server/controllers/signup.js.map +1 -1
- package/dist-server/controllers/unlock-user.js +0 -1
- package/dist-server/controllers/unlock-user.js.map +1 -1
- package/dist-server/controllers/verification.js +0 -1
- package/dist-server/controllers/verification.js.map +1 -1
- package/dist-server/middlewares/signin-middleware.js +4 -9
- package/dist-server/middlewares/signin-middleware.js.map +1 -1
- package/dist-server/middlewares/webauthn-middleware.js.map +1 -1
- package/dist-server/migrations/1548206416130-SeedUser.js +1 -2
- package/dist-server/migrations/1548206416130-SeedUser.js.map +1 -1
- package/dist-server/router/auth-checkin-router.js +2 -8
- package/dist-server/router/auth-checkin-router.js.map +1 -1
- package/dist-server/router/auth-private-process-router.js +7 -12
- package/dist-server/router/auth-private-process-router.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +9 -20
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/router/auth-signin-router.js +3 -3
- package/dist-server/router/auth-signin-router.js.map +1 -1
- package/dist-server/router/webauthn-router.js +1 -51
- package/dist-server/router/webauthn-router.js.map +1 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +2 -3
- package/dist-server/service/invitation/invitation-mutation.js +8 -20
- package/dist-server/service/invitation/invitation-mutation.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +9 -10
- package/dist-server/service/user/user-mutation.js +54 -112
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-types.d.ts +0 -1
- package/dist-server/service/user/user-types.js +0 -4
- package/dist-server/service/user/user-types.js.map +1 -1
- package/dist-server/service/user/user.d.ts +0 -1
- package/dist-server/service/user/user.js +14 -40
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/templates/account-unlock-email.d.ts +1 -2
- package/dist-server/templates/account-unlock-email.js +1 -1
- package/dist-server/templates/account-unlock-email.js.map +1 -1
- package/dist-server/templates/invitation-email.d.ts +1 -2
- package/dist-server/templates/invitation-email.js +1 -1
- package/dist-server/templates/invitation-email.js.map +1 -1
- package/dist-server/templates/verification-email.d.ts +1 -2
- package/dist-server/templates/verification-email.js +1 -1
- package/dist-server/templates/verification-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/constants/error-code.ts +20 -0
- package/server/constants/error-message.ts +0 -0
- package/server/constants/max-age.ts +1 -0
- package/server/controllers/auth.ts +5 -0
- package/server/controllers/change-pwd.ts +99 -0
- package/server/controllers/checkin.ts +21 -0
- package/server/controllers/delete-user.ts +68 -0
- package/server/controllers/invitation.ts +132 -0
- package/server/controllers/profile.ts +28 -0
- package/server/controllers/reset-password.ts +126 -0
- package/server/controllers/signin.ts +79 -0
- package/server/controllers/signup.ts +60 -0
- package/server/controllers/unlock-user.ts +61 -0
- package/server/controllers/utils/make-invitation-token.ts +5 -0
- package/server/controllers/utils/make-verification-token.ts +4 -0
- package/server/controllers/utils/password-rule.ts +120 -0
- package/server/controllers/utils/save-invitation-token.ts +10 -0
- package/server/controllers/utils/save-verification-token.ts +12 -0
- package/server/controllers/verification.ts +83 -0
- package/server/errors/auth-error.ts +24 -0
- package/server/errors/index.ts +2 -0
- package/server/errors/user-domain-not-match-error.ts +29 -0
- package/server/index.ts +37 -0
- package/server/middlewares/authenticate-401-middleware.ts +114 -0
- package/server/middlewares/domain-authenticate-middleware.ts +78 -0
- package/server/middlewares/graphql-authenticate-middleware.ts +13 -0
- package/server/middlewares/index.ts +67 -0
- package/server/middlewares/jwt-authenticate-middleware.ts +84 -0
- package/server/middlewares/signin-middleware.ts +55 -0
- package/server/middlewares/webauthn-middleware.ts +127 -0
- package/server/migrations/1548206416130-SeedUser.ts +59 -0
- package/server/migrations/1566805283882-SeedPrivilege.ts +28 -0
- package/server/migrations/index.ts +9 -0
- package/server/router/auth-checkin-router.ts +107 -0
- package/server/router/auth-private-process-router.ts +107 -0
- package/server/router/auth-public-process-router.ts +302 -0
- package/server/router/auth-signin-router.ts +55 -0
- package/server/router/auth-signup-router.ts +95 -0
- package/server/router/index.ts +9 -0
- package/server/router/oauth2/index.ts +2 -0
- package/server/router/oauth2/oauth2-authorize-router.ts +81 -0
- package/server/router/oauth2/oauth2-router.ts +165 -0
- package/server/router/oauth2/oauth2-server.ts +262 -0
- package/server/router/oauth2/passport-oauth2-client-password.ts +87 -0
- package/server/router/oauth2/passport-refresh-token.ts +87 -0
- package/server/router/path-base-domain-router.ts +8 -0
- package/server/router/site-root-router.ts +48 -0
- package/server/router/webauthn-router.ts +87 -0
- package/server/routes.ts +80 -0
- package/server/service/app-binding/app-binding-mutation.ts +22 -0
- package/server/service/app-binding/app-binding-query.ts +92 -0
- package/server/service/app-binding/app-binding-types.ts +11 -0
- package/server/service/app-binding/app-binding.ts +17 -0
- package/server/service/app-binding/index.ts +4 -0
- package/server/service/appliance/appliance-mutation.ts +113 -0
- package/server/service/appliance/appliance-query.ts +76 -0
- package/server/service/appliance/appliance-types.ts +56 -0
- package/server/service/appliance/appliance.ts +133 -0
- package/server/service/appliance/index.ts +6 -0
- package/server/service/application/application-mutation.ts +104 -0
- package/server/service/application/application-query.ts +98 -0
- package/server/service/application/application-types.ts +76 -0
- package/server/service/application/application.ts +216 -0
- package/server/service/application/index.ts +6 -0
- package/server/service/auth-provider/auth-provider-mutation.ts +159 -0
- package/server/service/auth-provider/auth-provider-parameter-spec.ts +24 -0
- package/server/service/auth-provider/auth-provider-query.ts +88 -0
- package/server/service/auth-provider/auth-provider-type.ts +67 -0
- package/server/service/auth-provider/auth-provider.ts +155 -0
- package/server/service/auth-provider/index.ts +7 -0
- package/server/service/domain-generator/domain-generator-mutation.ts +117 -0
- package/server/service/domain-generator/domain-generator-types.ts +46 -0
- package/server/service/domain-generator/index.ts +3 -0
- package/server/service/granted-role/granted-role-mutation.ts +156 -0
- package/server/service/granted-role/granted-role-query.ts +60 -0
- package/server/service/granted-role/granted-role.ts +27 -0
- package/server/service/granted-role/index.ts +6 -0
- package/server/service/index.ts +90 -0
- package/server/service/invitation/index.ts +6 -0
- package/server/service/invitation/invitation-mutation.ts +63 -0
- package/server/service/invitation/invitation-query.ts +33 -0
- package/server/service/invitation/invitation-types.ts +11 -0
- package/server/service/invitation/invitation.ts +63 -0
- package/server/service/login-history/index.ts +5 -0
- package/server/service/login-history/login-history-query.ts +51 -0
- package/server/service/login-history/login-history-type.ts +12 -0
- package/server/service/login-history/login-history.ts +45 -0
- package/server/service/partner/index.ts +6 -0
- package/server/service/partner/partner-mutation.ts +61 -0
- package/server/service/partner/partner-query.ts +102 -0
- package/server/service/partner/partner-types.ts +11 -0
- package/server/service/partner/partner.ts +57 -0
- package/server/service/password-history/index.ts +3 -0
- package/server/service/password-history/password-history.ts +16 -0
- package/server/service/privilege/index.ts +6 -0
- package/server/service/privilege/privilege-directive.ts +77 -0
- package/server/service/privilege/privilege-mutation.ts +92 -0
- package/server/service/privilege/privilege-query.ts +94 -0
- package/server/service/privilege/privilege-types.ts +60 -0
- package/server/service/privilege/privilege.ts +102 -0
- package/server/service/role/index.ts +6 -0
- package/server/service/role/role-mutation.ts +109 -0
- package/server/service/role/role-query.ts +155 -0
- package/server/service/role/role-types.ts +81 -0
- package/server/service/role/role.ts +72 -0
- package/server/service/user/domain-query.ts +24 -0
- package/server/service/user/index.ts +7 -0
- package/server/service/user/user-mutation.ts +413 -0
- package/server/service/user/user-query.ts +145 -0
- package/server/service/user/user-types.ts +97 -0
- package/server/service/user/user.ts +354 -0
- package/server/service/users-auth-providers/index.ts +5 -0
- package/server/service/users-auth-providers/users-auth-providers.ts +71 -0
- package/server/service/verification-token/index.ts +3 -0
- package/server/service/verification-token/verification-token.ts +60 -0
- package/server/service/web-auth-credential/index.ts +3 -0
- package/server/service/web-auth-credential/web-auth-credential.ts +67 -0
- package/server/templates/account-unlock-email.ts +65 -0
- package/server/templates/invitation-email.ts +66 -0
- package/server/templates/reset-password-email.ts +65 -0
- package/server/templates/verification-email.ts +66 -0
- package/server/types.ts +21 -0
- package/server/utils/accepts.ts +11 -0
- package/server/utils/access-token-cookie.ts +61 -0
- package/server/utils/check-permission.ts +52 -0
- package/server/utils/check-user-belongs-domain.ts +19 -0
- package/server/utils/check-user-has-role.ts +29 -0
- package/server/utils/encrypt-state.ts +22 -0
- package/server/utils/get-aes-256-key.ts +13 -0
- package/server/utils/get-domain-from-hostname.ts +7 -0
- package/server/utils/get-domain-users.ts +38 -0
- package/server/utils/get-secret.ts +13 -0
- package/server/utils/get-user-domains.ts +112 -0
- package/translations/en.json +1 -5
- package/translations/ja.json +1 -5
- package/translations/ko.json +3 -6
- package/translations/ms.json +1 -5
- package/translations/zh.json +1 -5
- package/dist-client/verify-webauthn.d.ts +0 -13
- package/dist-client/verify-webauthn.js +0 -72
- package/dist-client/verify-webauthn.js.map +0 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/auth-base",
|
3
|
-
"version": "8.0.0
|
3
|
+
"version": "8.0.0",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "dist-client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -32,10 +32,10 @@
|
|
32
32
|
"dependencies": {
|
33
33
|
"@simplewebauthn/browser": "^10.0.0",
|
34
34
|
"@simplewebauthn/server": "^10.0.0",
|
35
|
-
"@things-factory/email-base": "^8.0.0
|
36
|
-
"@things-factory/env": "^8.0.0
|
37
|
-
"@things-factory/shell": "^8.0.0
|
38
|
-
"@things-factory/utils": "^8.0.0
|
35
|
+
"@things-factory/email-base": "^8.0.0",
|
36
|
+
"@things-factory/env": "^8.0.0",
|
37
|
+
"@things-factory/shell": "^8.0.0",
|
38
|
+
"@things-factory/utils": "^8.0.0",
|
39
39
|
"@types/webappsec-credential-management": "^0.6.8",
|
40
40
|
"jsonwebtoken": "^9.0.0",
|
41
41
|
"koa-passport": "^6.0.0",
|
@@ -46,5 +46,5 @@
|
|
46
46
|
"passport-jwt": "^4.0.0",
|
47
47
|
"passport-local": "^1.0.0"
|
48
48
|
},
|
49
|
-
"gitHead": "
|
49
|
+
"gitHead": "07ef27d272dd9a067a9648ac7013748510556a18"
|
50
50
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
export const USER_NOT_FOUND = 'user not found'
|
2
|
+
export const PASSWORD_NOT_MATCHED = 'password-not-matched'
|
3
|
+
export const USER_NOT_ACTIVATED = 'user not activated'
|
4
|
+
export const USER_LOCKED = 'user-locked'
|
5
|
+
export const USER_DELETED = 'user-deleted'
|
6
|
+
export const NO_AVAILABLE_DOMAIN = 'no-available-domain'
|
7
|
+
export const UNAVAILABLE_DOMAIN = 'unavailable-domain'
|
8
|
+
export const NO_SELECTED_DOMAIN = 'no-selected-domain'
|
9
|
+
export const REDIRECT_TO_DEFAULT_DOMAIN = 'redirect-to-default-domain'
|
10
|
+
export const TOKEN_INVALID = 'token-invalid'
|
11
|
+
export const AUTH_INVALID = 'auth-invalid'
|
12
|
+
export const SUBDOMAIN_NOTFOUND = 'subdomain not found'
|
13
|
+
export const CONFIRM_PASSWORD_NOT_MATCHED = 'confirm password not matched'
|
14
|
+
export const PASSWORD_PATTERN_NOT_MATCHED = 'password should match the rule'
|
15
|
+
export const USER_DUPLICATED = 'user duplicated'
|
16
|
+
export const PASSWORD_USED_PAST = 'password used in the past'
|
17
|
+
export const VERIFICATION_ERROR = 'user or verification token not found'
|
18
|
+
export const AUTHN_VERIFICATION_FAILED = 'authn verification failed'
|
19
|
+
export const USER_CREDENTIAL_NOT_FOUND = 'user credential not found'
|
20
|
+
export const AUTH_ERROR = 'auth error'
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
export const MAX_AGE = 7 * 24 * 3600 * 1000
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { ILike } from 'typeorm'
|
2
|
+
import { config } from '@things-factory/env'
|
3
|
+
import { getRepository } from '@things-factory/shell'
|
4
|
+
|
5
|
+
import {
|
6
|
+
CONFIRM_PASSWORD_NOT_MATCHED,
|
7
|
+
PASSWORD_NOT_MATCHED,
|
8
|
+
PASSWORD_USED_PAST,
|
9
|
+
USER_NOT_FOUND
|
10
|
+
} from '../constants/error-code'
|
11
|
+
import { AuthError } from '../errors/auth-error'
|
12
|
+
import { PasswordHistory } from '../service/password-history/password-history'
|
13
|
+
import { User } from '../service/user/user'
|
14
|
+
|
15
|
+
const HISTORY_SIZE = config.get('password', { history: 0 }).history
|
16
|
+
|
17
|
+
export async function changePwd(attrs, currentPass, newPass, confirmPass, context) {
|
18
|
+
const { domain } = context.state
|
19
|
+
|
20
|
+
// TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.
|
21
|
+
const repository = getRepository(User)
|
22
|
+
const user: User = await repository.findOne({ where: { email: ILike(attrs.email) } })
|
23
|
+
|
24
|
+
if (!user) {
|
25
|
+
throw new AuthError({
|
26
|
+
errorCode: USER_NOT_FOUND
|
27
|
+
})
|
28
|
+
}
|
29
|
+
|
30
|
+
if (newPass !== confirmPass) {
|
31
|
+
throw new AuthError({
|
32
|
+
errorCode: CONFIRM_PASSWORD_NOT_MATCHED
|
33
|
+
})
|
34
|
+
}
|
35
|
+
|
36
|
+
if (!User.verify(user.password, currentPass, user.salt)) {
|
37
|
+
throw new AuthError({
|
38
|
+
errorCode: PASSWORD_NOT_MATCHED,
|
39
|
+
detail: {
|
40
|
+
email: user.email,
|
41
|
+
failCount: user.failCount
|
42
|
+
}
|
43
|
+
})
|
44
|
+
}
|
45
|
+
|
46
|
+
/* check if password is following the rule */
|
47
|
+
User.validatePasswordByRule(newPass, context?.lng)
|
48
|
+
|
49
|
+
user.password = User.encode(newPass, user.salt)
|
50
|
+
|
51
|
+
if (HISTORY_SIZE > 0) {
|
52
|
+
var passwordHistory: PasswordHistory = await getRepository(PasswordHistory).findOneBy({ userId: user.id })
|
53
|
+
var history = []
|
54
|
+
|
55
|
+
if (passwordHistory) {
|
56
|
+
try {
|
57
|
+
history = JSON.parse(passwordHistory.history)
|
58
|
+
if (!(history instanceof Array)) {
|
59
|
+
console.error('password history maybe currupted - not an array')
|
60
|
+
history = []
|
61
|
+
}
|
62
|
+
} catch (e) {
|
63
|
+
console.error('password history currupted - not json format')
|
64
|
+
}
|
65
|
+
|
66
|
+
const found = history.slice(0, HISTORY_SIZE).find(h => {
|
67
|
+
return User.verify(h.password, newPass, h.salt)
|
68
|
+
})
|
69
|
+
|
70
|
+
if (found) {
|
71
|
+
throw new AuthError({
|
72
|
+
errorCode: PASSWORD_USED_PAST
|
73
|
+
})
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
await repository.save({
|
79
|
+
...user,
|
80
|
+
passwordUpdatedAt: new Date()
|
81
|
+
})
|
82
|
+
|
83
|
+
if (HISTORY_SIZE > 0) {
|
84
|
+
history = [
|
85
|
+
{
|
86
|
+
password: user.password,
|
87
|
+
salt: user.salt
|
88
|
+
},
|
89
|
+
...history
|
90
|
+
].slice(0, HISTORY_SIZE)
|
91
|
+
|
92
|
+
await getRepository(PasswordHistory).save({
|
93
|
+
userId: user.id,
|
94
|
+
history: JSON.stringify(history)
|
95
|
+
})
|
96
|
+
}
|
97
|
+
|
98
|
+
return await user.sign({ subdomain: domain.subdomain })
|
99
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { Domain, getRepository } from '@things-factory/shell'
|
2
|
+
|
3
|
+
import { User } from '../service/user/user'
|
4
|
+
import { getUserDomains } from '../utils/get-user-domains'
|
5
|
+
|
6
|
+
export async function checkin({ userId, subdomain }) {
|
7
|
+
const userRepo = getRepository(User)
|
8
|
+
const user = await userRepo.findOne({ where: { id: userId } })
|
9
|
+
const domains: Partial<Domain>[] = await getUserDomains(user)
|
10
|
+
|
11
|
+
if (!domains?.length) {
|
12
|
+
return false
|
13
|
+
}
|
14
|
+
|
15
|
+
const domain = domains.find(domain => domain.subdomain == subdomain)
|
16
|
+
if (!domain) {
|
17
|
+
return false
|
18
|
+
}
|
19
|
+
|
20
|
+
return await user.sign({ subdomain })
|
21
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import { EntityManager, ILike, In } from 'typeorm'
|
2
|
+
import { User, UserStatus } from '../service/user/user'
|
3
|
+
import { AuthError } from '../errors/auth-error'
|
4
|
+
import { USER_NOT_FOUND } from '../constants/error-code'
|
5
|
+
|
6
|
+
export async function deleteUser(attrs, tx?: EntityManager) {
|
7
|
+
// TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.
|
8
|
+
// TODO 다른 도메인에도 포함되어있다면, domains-users 관게와 해당 도메인 관련 정보만 삭제해야 함.
|
9
|
+
|
10
|
+
const repository = tx?.getRepository(User)
|
11
|
+
const user = await repository.findOne({ where: { email: ILike(attrs.email) } })
|
12
|
+
if (!user) {
|
13
|
+
throw new AuthError({
|
14
|
+
errorCode: USER_NOT_FOUND
|
15
|
+
})
|
16
|
+
}
|
17
|
+
|
18
|
+
user.status = UserStatus.DELETED
|
19
|
+
user.domains = []
|
20
|
+
|
21
|
+
await repository.save(user)
|
22
|
+
|
23
|
+
// repository api는 작동하지 않음.
|
24
|
+
// await txManager
|
25
|
+
// .createQueryBuilder()
|
26
|
+
// .delete()
|
27
|
+
// .from('users_domains')
|
28
|
+
// .where({
|
29
|
+
// usersId: user.id
|
30
|
+
// })
|
31
|
+
// .execute()
|
32
|
+
}
|
33
|
+
|
34
|
+
export async function deleteUsers(attrs, tx?: EntityManager) {
|
35
|
+
// TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.
|
36
|
+
// TODO 다른 도메인에도 포함되어있다면, domains-users 관게와 해당 도메인 관련 정보만 삭제해야 함.
|
37
|
+
|
38
|
+
const { emails } = attrs
|
39
|
+
|
40
|
+
const repo = tx?.getRepository(User)
|
41
|
+
|
42
|
+
const users = await repo.find({
|
43
|
+
where: {
|
44
|
+
email: In(emails)
|
45
|
+
}
|
46
|
+
})
|
47
|
+
|
48
|
+
const userIds = []
|
49
|
+
users.forEach(user => {
|
50
|
+
user.status = UserStatus.DELETED
|
51
|
+
user.domains = []
|
52
|
+
|
53
|
+
userIds.push(user.id)
|
54
|
+
})
|
55
|
+
|
56
|
+
await repo.save(users)
|
57
|
+
|
58
|
+
// repository api는 작동하지 않음.
|
59
|
+
// await txManager
|
60
|
+
// .createQueryBuilder()
|
61
|
+
// .delete()
|
62
|
+
// .from('users_domains')
|
63
|
+
// .where({
|
64
|
+
// usersId: In(userIds)
|
65
|
+
// })
|
66
|
+
// .execute()
|
67
|
+
return true
|
68
|
+
}
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import { ILike } from 'typeorm'
|
2
|
+
import { URL } from 'url'
|
3
|
+
|
4
|
+
import { sendEmail } from '@things-factory/email-base'
|
5
|
+
import { Domain, getRepository } from '@things-factory/shell'
|
6
|
+
|
7
|
+
import { Invitation } from '../service/invitation/invitation'
|
8
|
+
import { User } from '../service/user/user'
|
9
|
+
import { getInvitationEmailForm } from '../templates/invitation-email'
|
10
|
+
import { makeInvitationToken } from './utils/make-invitation-token'
|
11
|
+
import { saveInvitationToken } from './utils/save-invitation-token'
|
12
|
+
|
13
|
+
export async function invite(attrs, withEmailInvitation?: Boolean) {
|
14
|
+
const { email, reference, type, context } = attrs
|
15
|
+
|
16
|
+
var user = await getRepository(User).findOne({ where: { email: ILike(email) }, relations: ['domains'] })
|
17
|
+
var domains = user.domains
|
18
|
+
|
19
|
+
// TODO reference should not be a domain.id (security reason)
|
20
|
+
|
21
|
+
if (user) {
|
22
|
+
const domain = domains.find(domain => domain.id == reference)
|
23
|
+
|
24
|
+
if (domain) {
|
25
|
+
const msg = `user already a member of the ${type}.`
|
26
|
+
throw new Error(msg)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
if (withEmailInvitation) {
|
31
|
+
var invitation = await getRepository(Invitation).findOneBy({
|
32
|
+
email: ILike(email),
|
33
|
+
reference,
|
34
|
+
type
|
35
|
+
})
|
36
|
+
|
37
|
+
if (!invitation) {
|
38
|
+
invitation = await getRepository(Invitation).save({
|
39
|
+
email,
|
40
|
+
reference,
|
41
|
+
type
|
42
|
+
})
|
43
|
+
}
|
44
|
+
|
45
|
+
return await sendInvitationEmail({
|
46
|
+
invitation,
|
47
|
+
context
|
48
|
+
})
|
49
|
+
}
|
50
|
+
|
51
|
+
if (user) {
|
52
|
+
user.domains = [...domains, await getRepository(Domain).findOneBy({ id: reference })]
|
53
|
+
await getRepository(User).save(user)
|
54
|
+
} else {
|
55
|
+
// TODO need to signup
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
export async function acceptInvitation(token) {
|
60
|
+
var invitation = await getRepository(Invitation).findOneBy({
|
61
|
+
token
|
62
|
+
})
|
63
|
+
|
64
|
+
if (!invitation) {
|
65
|
+
throw new Error(`not found invitation.`)
|
66
|
+
}
|
67
|
+
|
68
|
+
var { email, reference, type } = invitation
|
69
|
+
|
70
|
+
var user = await getRepository(User).findOne({ where: { email: ILike(email) }, relations: ['domains'] })
|
71
|
+
|
72
|
+
if (user) {
|
73
|
+
var domains = user.domains
|
74
|
+
const domain = domains.find(domain => domain.id == reference)
|
75
|
+
|
76
|
+
if (domain) {
|
77
|
+
const msg = `user already a member of the ${type}.`
|
78
|
+
throw new Error(msg)
|
79
|
+
}
|
80
|
+
|
81
|
+
user.domains = [...domains, await getRepository(Domain).findOneBy({ id: reference })]
|
82
|
+
await getRepository(User).save(user)
|
83
|
+
|
84
|
+
await getRepository(Invitation).delete(invitation.id)
|
85
|
+
} else {
|
86
|
+
// TODO goto signup
|
87
|
+
}
|
88
|
+
|
89
|
+
return true
|
90
|
+
}
|
91
|
+
|
92
|
+
export async function sendInvitationEmail({ invitation, context }) {
|
93
|
+
try {
|
94
|
+
var token = makeInvitationToken()
|
95
|
+
var verifaction = await saveInvitationToken(invitation.id, token)
|
96
|
+
|
97
|
+
if (verifaction) {
|
98
|
+
var serviceUrl = new URL(`/auth/accept/${token}`, context.header.referer)
|
99
|
+
|
100
|
+
await sendEmail({
|
101
|
+
receiver: invitation.email,
|
102
|
+
subject: 'Invitation',
|
103
|
+
content: getInvitationEmailForm({
|
104
|
+
email: invitation.email,
|
105
|
+
acceptUrl: serviceUrl
|
106
|
+
})
|
107
|
+
})
|
108
|
+
|
109
|
+
return true
|
110
|
+
}
|
111
|
+
} catch (e) {
|
112
|
+
return false
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
export async function resendInvitationEmail(
|
117
|
+
{ email, reference, type }: { email: string; reference: string; type: string },
|
118
|
+
context
|
119
|
+
) {
|
120
|
+
var invitation = await getRepository(Invitation).findOneBy({
|
121
|
+
email: ILike(email),
|
122
|
+
reference,
|
123
|
+
type
|
124
|
+
})
|
125
|
+
|
126
|
+
if (!invitation) return false
|
127
|
+
|
128
|
+
return await sendInvitationEmail({
|
129
|
+
invitation,
|
130
|
+
context
|
131
|
+
})
|
132
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
2
|
+
|
3
|
+
import { USER_NOT_FOUND } from '../constants/error-code'
|
4
|
+
import { AuthError } from '../errors/auth-error'
|
5
|
+
import { User } from '../service/user/user'
|
6
|
+
|
7
|
+
export async function updateProfile({ id }, newProfiles) {
|
8
|
+
const repository = getRepository(User)
|
9
|
+
const user = await repository.findOneBy({ id })
|
10
|
+
if (!user) {
|
11
|
+
throw new AuthError({
|
12
|
+
errorCode: USER_NOT_FOUND
|
13
|
+
})
|
14
|
+
}
|
15
|
+
|
16
|
+
/* only 'name', 'email' and 'locale' attributes can be changed */
|
17
|
+
var allowed = ['name', 'email', 'locale']
|
18
|
+
.filter(attr => attr in newProfiles)
|
19
|
+
.reduce((sum, attr) => {
|
20
|
+
sum[attr] = newProfiles[attr]
|
21
|
+
return sum
|
22
|
+
}, {})
|
23
|
+
|
24
|
+
return await repository.save({
|
25
|
+
...user,
|
26
|
+
...allowed
|
27
|
+
})
|
28
|
+
}
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import { URL } from 'url'
|
2
|
+
|
3
|
+
import { sendEmail } from '@things-factory/email-base'
|
4
|
+
import { config } from '@things-factory/env'
|
5
|
+
import { getRepository } from '@things-factory/shell'
|
6
|
+
|
7
|
+
import { PASSWORD_USED_PAST } from '../constants/error-code'
|
8
|
+
import { AuthError } from '../errors/auth-error'
|
9
|
+
import { PasswordHistory } from '../service/password-history/password-history'
|
10
|
+
import { User } from '../service/user/user'
|
11
|
+
import { VerificationToken, VerificationTokenType } from '../service/verification-token/verification-token'
|
12
|
+
import { getResetPasswordEmailForm } from '../templates/reset-password-email'
|
13
|
+
import { makeVerificationToken } from './utils/make-verification-token'
|
14
|
+
import { saveVerificationToken } from './utils/save-verification-token'
|
15
|
+
|
16
|
+
const HISTORY_SIZE = config.get('password', { history: 0 }).history
|
17
|
+
|
18
|
+
export async function sendPasswordResetEmail({ user, context }) {
|
19
|
+
try {
|
20
|
+
var token = makeVerificationToken()
|
21
|
+
var verifaction = await saveVerificationToken(user.id, token, VerificationTokenType.PASSWORD_RESET)
|
22
|
+
|
23
|
+
if (verifaction) {
|
24
|
+
var serviceUrl = new URL(`/auth/reset-password?token=${token}`, context.header.referer)
|
25
|
+
await sendEmail({
|
26
|
+
receiver: user.email,
|
27
|
+
subject: 'Reset your password',
|
28
|
+
content: getResetPasswordEmailForm({
|
29
|
+
name: user.name,
|
30
|
+
resetUrl: serviceUrl
|
31
|
+
})
|
32
|
+
})
|
33
|
+
|
34
|
+
return true
|
35
|
+
}
|
36
|
+
} catch (e) {
|
37
|
+
return false
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
export async function resetPassword(token, password, context) {
|
42
|
+
const { t } = context
|
43
|
+
|
44
|
+
const verificationToken = await getRepository(VerificationToken).findOne({
|
45
|
+
where: {
|
46
|
+
token,
|
47
|
+
type: VerificationTokenType.PASSWORD_RESET
|
48
|
+
}
|
49
|
+
})
|
50
|
+
|
51
|
+
if (!verificationToken) {
|
52
|
+
throw new Error(t('text.invalid verification token'))
|
53
|
+
}
|
54
|
+
|
55
|
+
const { userId } = verificationToken
|
56
|
+
if (!userId) {
|
57
|
+
throw new Error(t('text.invalid verification token'))
|
58
|
+
}
|
59
|
+
|
60
|
+
var user = await getRepository(User).findOneBy({ id: userId })
|
61
|
+
if (!user) {
|
62
|
+
throw new Error(t('error.user not found'))
|
63
|
+
}
|
64
|
+
|
65
|
+
// if (user.status == UserStatus.INACTIVE) {
|
66
|
+
// throw new Error(t('text.inactive user'))
|
67
|
+
// }
|
68
|
+
|
69
|
+
/* check if password is following the rule */
|
70
|
+
User.validatePasswordByRule(password, context?.lng)
|
71
|
+
|
72
|
+
user.password = User.encode(password, user.salt)
|
73
|
+
|
74
|
+
if (HISTORY_SIZE > 0) {
|
75
|
+
var passwordHistory: PasswordHistory = await getRepository(PasswordHistory).findOneBy({ userId: user.id })
|
76
|
+
var history = []
|
77
|
+
|
78
|
+
if (passwordHistory) {
|
79
|
+
try {
|
80
|
+
history = JSON.parse(passwordHistory.history)
|
81
|
+
if (!(history instanceof Array)) {
|
82
|
+
console.error('password history maybe currupted - not an array')
|
83
|
+
history = []
|
84
|
+
}
|
85
|
+
} catch (e) {
|
86
|
+
console.error('password history currupted - not json format')
|
87
|
+
}
|
88
|
+
|
89
|
+
const found = history.slice(0, HISTORY_SIZE).find(h => {
|
90
|
+
return User.verify(h.password, password, h.salt)
|
91
|
+
})
|
92
|
+
|
93
|
+
if (found) {
|
94
|
+
throw new AuthError({
|
95
|
+
errorCode: PASSWORD_USED_PAST
|
96
|
+
})
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
await getRepository(User).save({
|
102
|
+
...user,
|
103
|
+
passwordUpdatedAt: new Date()
|
104
|
+
})
|
105
|
+
|
106
|
+
await getRepository(VerificationToken).delete({
|
107
|
+
userId,
|
108
|
+
token,
|
109
|
+
type: VerificationTokenType.PASSWORD_RESET
|
110
|
+
})
|
111
|
+
|
112
|
+
if (HISTORY_SIZE > 0) {
|
113
|
+
history = [
|
114
|
+
{
|
115
|
+
password: user.password,
|
116
|
+
salt: user.salt
|
117
|
+
},
|
118
|
+
...history
|
119
|
+
].slice(0, HISTORY_SIZE)
|
120
|
+
|
121
|
+
await getRepository(PasswordHistory).save({
|
122
|
+
userId: user.id,
|
123
|
+
history: JSON.stringify(history)
|
124
|
+
})
|
125
|
+
}
|
126
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { ILike } from 'typeorm'
|
2
|
+
import { getRepository } from '@things-factory/shell'
|
3
|
+
|
4
|
+
import { sendUnlockUserEmail } from '../controllers/unlock-user'
|
5
|
+
import { AuthError } from '../errors/auth-error'
|
6
|
+
import { User, UserStatus } from '../service/user/user'
|
7
|
+
|
8
|
+
export async function signin(attrs, context?) {
|
9
|
+
const { domain } = context?.state || {}
|
10
|
+
|
11
|
+
const repository = getRepository(User)
|
12
|
+
const user = await repository.findOne({ where: { email: ILike(attrs.email) }, relations: ['domains'] })
|
13
|
+
if (!user)
|
14
|
+
throw new AuthError({
|
15
|
+
errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND
|
16
|
+
})
|
17
|
+
|
18
|
+
if (user.status == UserStatus.DELETED) {
|
19
|
+
throw new AuthError({
|
20
|
+
errorCode: AuthError.ERROR_CODES.USER_DELETED
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|
24
|
+
if (user.status == UserStatus.LOCKED) {
|
25
|
+
sendUnlockUserEmail({
|
26
|
+
user,
|
27
|
+
context
|
28
|
+
})
|
29
|
+
throw new AuthError({
|
30
|
+
errorCode: AuthError.ERROR_CODES.USER_LOCKED,
|
31
|
+
detail: {
|
32
|
+
email: user.email
|
33
|
+
}
|
34
|
+
})
|
35
|
+
}
|
36
|
+
|
37
|
+
if (!User.verify(user.password, attrs.password, user.salt)) {
|
38
|
+
user.failCount++
|
39
|
+
if (user.failCount >= 5) user.status = UserStatus.LOCKED
|
40
|
+
await repository.save(user)
|
41
|
+
if (user.status == UserStatus.LOCKED) {
|
42
|
+
sendUnlockUserEmail({
|
43
|
+
user,
|
44
|
+
context
|
45
|
+
})
|
46
|
+
throw new AuthError({
|
47
|
+
errorCode: AuthError.ERROR_CODES.USER_LOCKED,
|
48
|
+
detail: {
|
49
|
+
email: user.email
|
50
|
+
}
|
51
|
+
})
|
52
|
+
}
|
53
|
+
throw new AuthError({
|
54
|
+
errorCode: AuthError.ERROR_CODES.PASSWORD_NOT_MATCHED,
|
55
|
+
detail: {
|
56
|
+
email: user.email,
|
57
|
+
failCount: user.failCount
|
58
|
+
}
|
59
|
+
})
|
60
|
+
} else {
|
61
|
+
user.failCount = 0
|
62
|
+
await repository.save(user)
|
63
|
+
}
|
64
|
+
|
65
|
+
if (user.status == UserStatus.INACTIVE) {
|
66
|
+
throw new AuthError({
|
67
|
+
errorCode: AuthError.ERROR_CODES.USER_NOT_ACTIVATED,
|
68
|
+
detail: {
|
69
|
+
email: user.email
|
70
|
+
}
|
71
|
+
})
|
72
|
+
}
|
73
|
+
|
74
|
+
return {
|
75
|
+
user,
|
76
|
+
token: await user.sign({ subdomain: domain?.subdomain }),
|
77
|
+
domains: user.domains || []
|
78
|
+
}
|
79
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { ILike } from 'typeorm'
|
2
|
+
import { getRepository } from '@things-factory/shell'
|
3
|
+
|
4
|
+
import { USER_DUPLICATED } from '../constants/error-code'
|
5
|
+
import { AuthError } from '../errors/auth-error'
|
6
|
+
import { User } from '../service/user/user'
|
7
|
+
import { signin } from './signin'
|
8
|
+
import { sendVerificationEmail } from './verification'
|
9
|
+
|
10
|
+
export async function signup(attrs, withEmailVerification?: Boolean) {
|
11
|
+
const { name, email, password, domain, context } = attrs
|
12
|
+
|
13
|
+
/* check if password is following the rule */
|
14
|
+
User.validatePasswordByRule(password, context.lng)
|
15
|
+
|
16
|
+
const repository = getRepository(User)
|
17
|
+
const duplicated = await repository.findOneBy({ email: ILike(email) })
|
18
|
+
if (duplicated) {
|
19
|
+
throw new AuthError({
|
20
|
+
errorCode: USER_DUPLICATED,
|
21
|
+
detail: {
|
22
|
+
name,
|
23
|
+
email
|
24
|
+
}
|
25
|
+
})
|
26
|
+
}
|
27
|
+
|
28
|
+
const salt = User.generateSalt()
|
29
|
+
|
30
|
+
var user = await repository.save({
|
31
|
+
userType: 'user',
|
32
|
+
...attrs,
|
33
|
+
salt,
|
34
|
+
password: User.encode(password, salt),
|
35
|
+
passwordUpdatedAt: new Date(),
|
36
|
+
domains: domain ? [domain] : []
|
37
|
+
})
|
38
|
+
|
39
|
+
var succeed = false
|
40
|
+
if (withEmailVerification) {
|
41
|
+
succeed = await sendVerificationEmail({
|
42
|
+
context,
|
43
|
+
user
|
44
|
+
})
|
45
|
+
}
|
46
|
+
|
47
|
+
try {
|
48
|
+
return {
|
49
|
+
token: await signin(
|
50
|
+
{
|
51
|
+
email,
|
52
|
+
password
|
53
|
+
},
|
54
|
+
{ domain }
|
55
|
+
)
|
56
|
+
}
|
57
|
+
} catch (e) {
|
58
|
+
return { token: null }
|
59
|
+
}
|
60
|
+
}
|