@things-factory/auth-base 7.0.1-alpha.1 → 7.0.1-alpha.100

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.
Files changed (95) hide show
  1. package/dist-client/directive/privileged.d.ts +1 -1
  2. package/dist-client/tsconfig.tsbuildinfo +1 -1
  3. package/dist-server/constants/error-code.d.ts +2 -0
  4. package/dist-server/constants/error-code.js +3 -1
  5. package/dist-server/constants/error-code.js.map +1 -1
  6. package/dist-server/controllers/profile.d.ts +1 -0
  7. package/dist-server/middlewares/authenticate-401-middleware.js +1 -1
  8. package/dist-server/middlewares/authenticate-401-middleware.js.map +1 -1
  9. package/dist-server/middlewares/index.d.ts +1 -0
  10. package/dist-server/middlewares/index.js +2 -1
  11. package/dist-server/middlewares/index.js.map +1 -1
  12. package/dist-server/middlewares/webauthn-middleware.d.ts +2 -0
  13. package/dist-server/middlewares/webauthn-middleware.js +62 -0
  14. package/dist-server/middlewares/webauthn-middleware.js.map +1 -0
  15. package/dist-server/router/auth-private-process-router.js +7 -1
  16. package/dist-server/router/auth-private-process-router.js.map +1 -1
  17. package/dist-server/router/index.d.ts +1 -0
  18. package/dist-server/router/index.js +1 -0
  19. package/dist-server/router/index.js.map +1 -1
  20. package/dist-server/router/webauthn-router.d.ts +2 -0
  21. package/dist-server/router/webauthn-router.js +45 -0
  22. package/dist-server/router/webauthn-router.js.map +1 -0
  23. package/dist-server/routes.js +3 -1
  24. package/dist-server/routes.js.map +1 -1
  25. package/dist-server/service/appliance/appliance.d.ts +1 -1
  26. package/dist-server/service/appliance/appliance.js +1 -0
  27. package/dist-server/service/appliance/appliance.js.map +1 -1
  28. package/dist-server/service/application/application.js +1 -0
  29. package/dist-server/service/application/application.js.map +1 -1
  30. package/dist-server/service/auth-provider/auth-provider-parameter-spec.d.ts +3 -0
  31. package/dist-server/service/auth-provider/auth-provider-parameter-spec.js +4 -0
  32. package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +1 -1
  33. package/dist-server/service/auth-provider/auth-provider-type.js.map +1 -1
  34. package/dist-server/service/auth-provider/auth-provider.d.ts +0 -5
  35. package/dist-server/service/auth-provider/auth-provider.js +2 -16
  36. package/dist-server/service/auth-provider/auth-provider.js.map +1 -1
  37. package/dist-server/service/granted-role/granted-role.d.ts +1 -1
  38. package/dist-server/service/granted-role/granted-role.js +1 -1
  39. package/dist-server/service/granted-role/granted-role.js.map +1 -1
  40. package/dist-server/service/index.d.ts +2 -1
  41. package/dist-server/service/index.js +4 -1
  42. package/dist-server/service/index.js.map +1 -1
  43. package/dist-server/service/login-history/login-history.d.ts +1 -1
  44. package/dist-server/service/login-history/login-history.js +2 -2
  45. package/dist-server/service/login-history/login-history.js.map +1 -1
  46. package/dist-server/service/partner/partner.d.ts +2 -2
  47. package/dist-server/service/partner/partner.js +4 -4
  48. package/dist-server/service/partner/partner.js.map +1 -1
  49. package/dist-server/service/role/role.d.ts +1 -1
  50. package/dist-server/service/role/role.js +2 -1
  51. package/dist-server/service/role/role.js.map +1 -1
  52. package/dist-server/service/user/user.d.ts +2 -0
  53. package/dist-server/service/user/user.js +14 -24
  54. package/dist-server/service/user/user.js.map +1 -1
  55. package/dist-server/service/users-auth-providers/users-auth-providers.js +1 -1
  56. package/dist-server/service/users-auth-providers/users-auth-providers.js.map +1 -1
  57. package/dist-server/service/web-auth-credential/index.d.ts +2 -0
  58. package/dist-server/service/web-auth-credential/index.js +6 -0
  59. package/dist-server/service/web-auth-credential/index.js.map +1 -0
  60. package/dist-server/service/web-auth-credential/web-auth-credential.d.ts +15 -0
  61. package/dist-server/service/web-auth-credential/web-auth-credential.js +72 -0
  62. package/dist-server/service/web-auth-credential/web-auth-credential.js.map +1 -0
  63. package/dist-server/tsconfig.tsbuildinfo +1 -1
  64. package/dist-server/utils/access-token-cookie.d.ts +1 -0
  65. package/dist-server/utils/access-token-cookie.js +12 -1
  66. package/dist-server/utils/access-token-cookie.js.map +1 -1
  67. package/package.json +9 -7
  68. package/server/constants/error-code.ts +2 -0
  69. package/server/middlewares/authenticate-401-middleware.ts +1 -1
  70. package/server/middlewares/index.ts +2 -1
  71. package/server/middlewares/webauthn-middleware.ts +75 -0
  72. package/server/router/auth-private-process-router.ts +8 -1
  73. package/server/router/index.ts +1 -0
  74. package/server/router/webauthn-router.ts +55 -0
  75. package/server/routes.ts +7 -8
  76. package/server/service/appliance/appliance.ts +4 -3
  77. package/server/service/application/application.ts +5 -4
  78. package/server/service/auth-provider/auth-provider-parameter-spec.ts +3 -0
  79. package/server/service/auth-provider/auth-provider-type.ts +3 -7
  80. package/server/service/auth-provider/auth-provider.ts +3 -19
  81. package/server/service/granted-role/granted-role.ts +2 -2
  82. package/server/service/index.ts +5 -5
  83. package/server/service/login-history/login-history.ts +3 -3
  84. package/server/service/partner/partner.ts +6 -6
  85. package/server/service/role/role.ts +3 -2
  86. package/server/service/user/user.ts +12 -22
  87. package/server/service/users-auth-providers/users-auth-providers.ts +1 -1
  88. package/server/service/web-auth-credential/index.ts +3 -0
  89. package/server/service/web-auth-credential/web-auth-credential.ts +66 -0
  90. package/server/utils/access-token-cookie.ts +12 -0
  91. package/translations/en.json +31 -28
  92. package/translations/ja.json +32 -29
  93. package/translations/ko.json +32 -29
  94. package/translations/ms.json +3 -0
  95. package/translations/zh.json +3 -0
@@ -1,3 +1,4 @@
1
1
  export declare function getAccessTokenCookie(context: any): any;
2
2
  export declare function setAccessTokenCookie(context: any, token: any): void;
3
+ export declare function setSessionAccessToken(context: any): void;
3
4
  export declare function clearAccessTokenCookie(context: any): void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.clearAccessTokenCookie = exports.setAccessTokenCookie = exports.getAccessTokenCookie = void 0;
3
+ exports.clearAccessTokenCookie = exports.setSessionAccessToken = exports.setAccessTokenCookie = exports.getAccessTokenCookie = void 0;
4
4
  const shell_1 = require("@things-factory/shell");
5
5
  const env_1 = require("@things-factory/env");
6
6
  const max_age_1 = require("../constants/max-age");
@@ -24,6 +24,16 @@ function setAccessTokenCookie(context, token) {
24
24
  context.cookies.set(accessTokenCookieKey, token, cookie);
25
25
  }
26
26
  exports.setAccessTokenCookie = setAccessTokenCookie;
27
+ function setSessionAccessToken(context) {
28
+ /* koa-session 을 사용하는 경우에는, cookie 직접 설정이 작동되지 않는다. 그런 경우에는 session에 설정해서 cookie를 변경한다. */
29
+ const { user } = context.state;
30
+ context.session = {
31
+ id: user.id,
32
+ userType: user.type,
33
+ status: user.state
34
+ };
35
+ }
36
+ exports.setSessionAccessToken = setSessionAccessToken;
27
37
  function clearAccessTokenCookie(context) {
28
38
  const { secure } = context;
29
39
  var cookie = {
@@ -40,6 +50,7 @@ function clearAccessTokenCookie(context) {
40
50
  * https://github.com/hatiolab/things-factory/issues/70
41
51
  */
42
52
  context.cookies.set('i18next', '', cookie);
53
+ context.session = null;
43
54
  }
44
55
  exports.clearAccessTokenCookie = clearAccessTokenCookie;
45
56
  //# sourceMappingURL=access-token-cookie.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"access-token-cookie.js","sourceRoot":"","sources":["../../server/utils/access-token-cookie.ts"],"names":[],"mappings":";;;AAAA,iDAAmE;AACnE,6CAA4C;AAC5C,kDAA8C;AAE9C,MAAM,oBAAoB,GAAG,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAA;AAE/E,SAAgB,oBAAoB,CAAC,OAAO;;IAC1C,OAAO,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,0CAAE,GAAG,CAAC,oBAAoB,CAAC,CAAA;AACpD,CAAC;AAFD,oDAEC;AAED,SAAgB,oBAAoB,CAAC,OAAO,EAAE,KAAK;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,IAAI,MAAM,GAAG;QACX,MAAM;QACN,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,iBAAO;KAChB,CAAA;IAED,MAAM,YAAY,GAAG,IAAA,mCAA2B,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClE,IAAI,YAAY,EAAE;QAChB,MAAM,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAA;KAChC;IAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;AAC1D,CAAC;AAfD,oDAeC;AAED,SAAgB,sBAAsB,CAAC,OAAO;IAC5C,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,IAAI,MAAM,GAAG;QACX,MAAM;QACN,QAAQ,EAAE,IAAI;KACf,CAAA;IAED,MAAM,YAAY,GAAG,IAAA,mCAA2B,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClE,IAAI,YAAY,EAAE;QAChB,MAAM,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAA;KAChC;IAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;IACrD;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;AAC5C,CAAC;AAnBD,wDAmBC","sourcesContent":["import { getCookieDomainFromHostname } from '@things-factory/shell'\nimport { config } from '@things-factory/env'\nimport { MAX_AGE } from '../constants/max-age'\n\nconst accessTokenCookieKey = config.get('accessTokenCookieKey', 'access_token')\n\nexport function getAccessTokenCookie(context) {\n return context?.cookies?.get(accessTokenCookieKey)\n}\n\nexport function setAccessTokenCookie(context, token) {\n const { secure } = context\n\n var cookie = {\n secure,\n httpOnly: true,\n maxAge: MAX_AGE\n }\n\n const cookieDomain = getCookieDomainFromHostname(context.hostname)\n if (cookieDomain) {\n cookie['domain'] = cookieDomain\n }\n\n context.cookies.set(accessTokenCookieKey, token, cookie)\n}\n\nexport function clearAccessTokenCookie(context) {\n const { secure } = context\n\n var cookie = {\n secure,\n httpOnly: true\n }\n\n const cookieDomain = getCookieDomainFromHostname(context.hostname)\n if (cookieDomain) {\n cookie['domain'] = cookieDomain\n }\n\n context.cookies.set(accessTokenCookieKey, '', cookie)\n /*\n * TODO clear i18next cookie as well - need to support domain\n * https://github.com/hatiolab/things-factory/issues/70\n */\n context.cookies.set('i18next', '', cookie)\n}\n"]}
1
+ {"version":3,"file":"access-token-cookie.js","sourceRoot":"","sources":["../../server/utils/access-token-cookie.ts"],"names":[],"mappings":";;;AAAA,iDAAmE;AACnE,6CAA4C;AAC5C,kDAA8C;AAE9C,MAAM,oBAAoB,GAAG,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAA;AAE/E,SAAgB,oBAAoB,CAAC,OAAO;;IAC1C,OAAO,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,0CAAE,GAAG,CAAC,oBAAoB,CAAC,CAAA;AACpD,CAAC;AAFD,oDAEC;AAED,SAAgB,oBAAoB,CAAC,OAAO,EAAE,KAAK;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,IAAI,MAAM,GAAG;QACX,MAAM;QACN,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,iBAAO;KAChB,CAAA;IAED,MAAM,YAAY,GAAG,IAAA,mCAA2B,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClE,IAAI,YAAY,EAAE;QAChB,MAAM,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAA;KAChC;IAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;AAC1D,CAAC;AAfD,oDAeC;AAED,SAAgB,qBAAqB,CAAC,OAAO;IAC3C,0FAA0F;IAC1F,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAE9B,OAAO,CAAC,OAAO,GAAG;QAChB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,MAAM,EAAE,IAAI,CAAC,KAAK;KACnB,CAAA;AACH,CAAC;AATD,sDASC;AAED,SAAgB,sBAAsB,CAAC,OAAO;IAC5C,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE1B,IAAI,MAAM,GAAG;QACX,MAAM;QACN,QAAQ,EAAE,IAAI;KACf,CAAA;IAED,MAAM,YAAY,GAAG,IAAA,mCAA2B,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClE,IAAI,YAAY,EAAE;QAChB,MAAM,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAA;KAChC;IAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;IACrD;;;OAGG;IACH,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;IAC1C,OAAO,CAAC,OAAO,GAAG,IAAI,CAAA;AACxB,CAAC;AApBD,wDAoBC","sourcesContent":["import { getCookieDomainFromHostname } from '@things-factory/shell'\nimport { config } from '@things-factory/env'\nimport { MAX_AGE } from '../constants/max-age'\n\nconst accessTokenCookieKey = config.get('accessTokenCookieKey', 'access_token')\n\nexport function getAccessTokenCookie(context) {\n return context?.cookies?.get(accessTokenCookieKey)\n}\n\nexport function setAccessTokenCookie(context, token) {\n const { secure } = context\n\n var cookie = {\n secure,\n httpOnly: true,\n maxAge: MAX_AGE\n }\n\n const cookieDomain = getCookieDomainFromHostname(context.hostname)\n if (cookieDomain) {\n cookie['domain'] = cookieDomain\n }\n\n context.cookies.set(accessTokenCookieKey, token, cookie)\n}\n\nexport function setSessionAccessToken(context) {\n /* koa-session 을 사용하는 경우에는, cookie 직접 설정이 작동되지 않는다. 그런 경우에는 session에 설정해서 cookie를 변경한다. */\n const { user } = context.state\n\n context.session = {\n id: user.id,\n userType: user.type,\n status: user.state\n }\n}\n\nexport function clearAccessTokenCookie(context) {\n const { secure } = context\n\n var cookie = {\n secure,\n httpOnly: true\n }\n\n const cookieDomain = getCookieDomainFromHostname(context.hostname)\n if (cookieDomain) {\n cookie['domain'] = cookieDomain\n }\n\n context.cookies.set(accessTokenCookieKey, '', cookie)\n /*\n * TODO clear i18next cookie as well - need to support domain\n * https://github.com/hatiolab/things-factory/issues/70\n */\n context.cookies.set('i18next', '', cookie)\n context.session = null\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-base",
3
- "version": "7.0.1-alpha.1",
3
+ "version": "7.0.1-alpha.100",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -30,18 +30,20 @@
30
30
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create -d ./server/migrations"
31
31
  },
32
32
  "dependencies": {
33
- "@things-factory/email-base": "^7.0.1-alpha.1",
34
- "@things-factory/env": "^7.0.1-alpha.0",
35
- "@things-factory/i18n-base": "^7.0.1-alpha.1",
36
- "@things-factory/shell": "^7.0.1-alpha.1",
37
- "@things-factory/utils": "^7.0.1-alpha.0",
33
+ "@things-factory/email-base": "^7.0.1-alpha.100",
34
+ "@things-factory/env": "^7.0.1-alpha.71",
35
+ "@things-factory/shell": "^7.0.1-alpha.100",
36
+ "@things-factory/utils": "^7.0.1-alpha.71",
37
+ "@types/webappsec-credential-management": "^0.6.8",
38
38
  "jsonwebtoken": "^9.0.0",
39
39
  "koa-passport": "^6.0.0",
40
40
  "koa-session": "^6.4.0",
41
41
  "oauth2orize-koa": "^1.3.2",
42
+ "passport": "^0.7.0",
43
+ "passport-fido2-webauthn": "^0.1.0",
42
44
  "passport-jwt": "^4.0.0",
43
45
  "passport-local": "^1.0.0",
44
46
  "popsicle-cookie-jar": "^1.0.0"
45
47
  },
46
- "gitHead": "6cc9a9da9059f32f8c2968c57831c1d353a2ec97"
48
+ "gitHead": "f5062c5559383d06fc8f9f870b56163952557c70"
47
49
  }
@@ -15,3 +15,5 @@ export const PASSWORD_PATTERN_NOT_MATCHED = 'password should match the rule'
15
15
  export const USER_DUPLICATED = 'user duplicated'
16
16
  export const PASSWORD_USED_PAST = 'password used in the past'
17
17
  export const VERIFICATION_ERROR = 'user or verification token not found'
18
+ export const USER_CREDENTIAL_NOT_FOUND = 'user credential not found'
19
+ export const AUTH_ERROR = 'auth error'
@@ -15,7 +15,7 @@ export async function authenticate401Middleware(context, next) {
15
15
  var message
16
16
 
17
17
  if (err instanceof AuthError) {
18
- message = context.t(`error.${err.errorCode}`, err.detail || {})
18
+ message = (context.t && context.t(`error.${err.errorCode}`, err.detail || {})) || err.errorCode
19
19
  } else {
20
20
  if (err?.status !== 401) {
21
21
  throw err
@@ -20,7 +20,7 @@ export function initMiddlewares(app: any) {
20
20
  app.use(
21
21
  session(
22
22
  {
23
- key: accessTokenCookieKey,
23
+ key: 'tfsession',
24
24
  maxAge: MAX_AGE,
25
25
  overwrite: true,
26
26
  httpOnly: true,
@@ -63,4 +63,5 @@ process.on('bootstrap-module-subscription' as any, (app, subscriptionMiddleware)
63
63
  export * from './jwt-authenticate-middleware'
64
64
  export * from './domain-authenticate-middleware'
65
65
  export * from './signin-middleware'
66
+ export * from './webauthn-middleware'
66
67
  export * from './authenticate-401-middleware'
@@ -0,0 +1,75 @@
1
+ import passport from 'koa-passport'
2
+ import { Strategy as WebAuthnStrategy, SessionChallengeStore } from 'passport-fido2-webauthn'
3
+
4
+ import { getRepository } from '@things-factory/shell'
5
+
6
+ import { User } from '../service/user/user'
7
+ import { AuthError } from '../errors/auth-error'
8
+ import { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential'
9
+
10
+ export const store = new SessionChallengeStore()
11
+
12
+ passport.use(
13
+ new WebAuthnStrategy(
14
+ { store },
15
+ async function verify(id: string, userHandle: Uint8Array, cb) {
16
+ const user = await getRepository(User).findOne({ where: { id: userHandle.toString() } })
17
+ if (!user) {
18
+ return cb(null, false, { errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND })
19
+ }
20
+ const credential = await getRepository(WebAuthCredential).findOne({
21
+ where: { credentialId: id, user: { id: user.id } }
22
+ })
23
+ if (!credential) {
24
+ return cb(null, false, { errorCode: AuthError.ERROR_CODES.USER_CREDENTIAL_NOT_FOUND })
25
+ }
26
+
27
+ return cb(null, user, credential.publicKey)
28
+ },
29
+ async function register(user, id, publicKey, cb) {
30
+ const userObject = await getRepository(User).findOne({ where: { id: user.id.toString() } })
31
+ const webAuthRepository = getRepository(WebAuthCredential)
32
+
33
+ const oldCredential = await webAuthRepository.findOne({
34
+ where: { user: { id: userObject.id }, publicKey: publicKey }
35
+ })
36
+
37
+ /* TODO publicKey 비교로는 중복된 등록을 막을 수 없다. */
38
+ if (oldCredential) {
39
+ await webAuthRepository.delete(oldCredential.id)
40
+ }
41
+
42
+ await webAuthRepository.save({
43
+ user: userObject,
44
+ credentialId: id,
45
+ publicKey,
46
+ counter: 0
47
+ })
48
+
49
+ return cb(null, userObject)
50
+ }
51
+ )
52
+ )
53
+
54
+ export async function webAuthnMiddleware(context, next) {
55
+ return passport.authenticate(
56
+ 'webauthn',
57
+ { session: true, failureMessage: true, failWithError: true },
58
+ async (err, user, info) => {
59
+ if (err || !user) {
60
+ if (info.errorCode) {
61
+ throw new AuthError(info)
62
+ } else {
63
+ throw new AuthError({
64
+ errorCode: AuthError.ERROR_CODES.AUTH_ERROR,
65
+ detail: info
66
+ })
67
+ }
68
+ } else {
69
+ context.state.user = user
70
+
71
+ await next()
72
+ }
73
+ }
74
+ )(context, next)
75
+ }
@@ -41,7 +41,7 @@ authPrivateProcessRouter
41
41
  }
42
42
  })
43
43
  .post('/delete-user', async (context, next) => {
44
- const { t } = context
44
+ const { t, session } = context
45
45
  var { user } = context.state
46
46
  var { email: userEmail } = user
47
47
 
@@ -67,8 +67,15 @@ authPrivateProcessRouter
67
67
  clearAccessTokenCookie(context)
68
68
  })
69
69
  .get('/profile', async (context, next) => {
70
+ const { t } = context
70
71
  const { domain, user, unsafeIP, prohibitedPrivileges } = context.state
71
72
 
73
+ if (!domain) {
74
+ context.status = 401
75
+ context.body = t('error.user validation failed')
76
+ return
77
+ }
78
+
72
79
  let domains: Partial<Domain>[] = await getUserDomains(user)
73
80
  domains = domains.filter((d: Domain) => d.extType == domainType)
74
81
 
@@ -6,3 +6,4 @@ export * from './oauth2'
6
6
  export * from './auth-checkin-router'
7
7
  export * from './auth-signin-router'
8
8
  export * from './auth-signup-router'
9
+ export * from './webauthn-router'
@@ -0,0 +1,55 @@
1
+ import util from 'util'
2
+ import Router from 'koa-router'
3
+
4
+ import { setAccessTokenCookie } from '../utils/access-token-cookie'
5
+ import { store, webAuthnMiddleware } from '../middlewares/webauthn-middleware'
6
+
7
+ export const webAuthnGlobalPublicRouter = new Router()
8
+ export const webAuthnGlobalPrivateRouter = new Router()
9
+
10
+ const challengeAsync = util.promisify(store.challenge).bind(store)
11
+
12
+ webAuthnGlobalPublicRouter.post('/auth/signin-webauthn/challenge', async (context, next) => {
13
+ const challenge = await challengeAsync({ ...context.request, session: context.session })
14
+
15
+ context.body = {
16
+ challenge: Buffer.from(challenge).toString('base64')
17
+ }
18
+ })
19
+
20
+ webAuthnGlobalPublicRouter.post('/auth/signin-webauthn', webAuthnMiddleware, async (context, next) => {
21
+ const { domain, user } = context.state
22
+ const { request } = context
23
+ const { body: reqBody } = request
24
+
25
+ const token = await user.sign({ subdomain: domain?.subdomain })
26
+ setAccessTokenCookie(context, token)
27
+
28
+ var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`
29
+
30
+ /* 2단계 인터렉션 때문에 브라우저에서 fetch(...)로 진행될 것이므로, redirect(3xx) 응답으로 처리할 수 없다. 따라서, 데이타로 redirectURL를 응답한다. */
31
+ context.body = { redirectURL }
32
+ })
33
+
34
+ webAuthnGlobalPrivateRouter.post('/auth/register-webauthn/challenge', async (context, next) => {
35
+ const { user } = context.state
36
+ const { id, name } = user || {}
37
+
38
+ const challenge = await challengeAsync(
39
+ { ...context.request, session: context.session },
40
+ {
41
+ user: {
42
+ id
43
+ }
44
+ }
45
+ )
46
+
47
+ context.body = {
48
+ user: {
49
+ id: Buffer.from(id).toString('base64'),
50
+ name: name,
51
+ displayName: name
52
+ },
53
+ challenge: Buffer.from(challenge).toString('base64')
54
+ }
55
+ })
package/server/routes.ts CHANGED
@@ -10,11 +10,12 @@ import {
10
10
  oauth2AuthorizeRouter,
11
11
  oauth2Router,
12
12
  pathBaseDomainRouter,
13
- siteRootRouter
13
+ siteRootRouter,
14
+ webAuthnGlobalPublicRouter,
15
+ webAuthnGlobalPrivateRouter
14
16
  } from './router'
15
17
 
16
18
  import { setAccessTokenCookie } from './utils/access-token-cookie'
17
- import { User } from './service/user/user'
18
19
 
19
20
  const isPathBaseDomain = !config.get('subdomain') && !config.get('useVirtualHostBasedDomain')
20
21
 
@@ -27,7 +28,7 @@ process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRout
27
28
  authSigninRouter.get('/auth/sso-signin', app.ssoMiddlewares[0], async context => {
28
29
  const { user } = context.state
29
30
 
30
- const token = user.sign()
31
+ const token = await user.sign()
31
32
  setAccessTokenCookie(context, token)
32
33
 
33
34
  context.redirect('/auth/checkin')
@@ -41,12 +42,14 @@ process.on('bootstrap-module-global-private-route' as any, (app, globalPrivateRo
41
42
  /* globalPrivateRouter based nested-routers */
42
43
  globalPrivateRouter.use(authCheckinRouter.routes(), authCheckinRouter.allowedMethods())
43
44
  globalPrivateRouter.use(authPrivateProcessRouter.routes(), authPrivateProcessRouter.allowedMethods())
45
+ globalPrivateRouter.use(webAuthnGlobalPrivateRouter.routes(), webAuthnGlobalPrivateRouter.allowedMethods())
44
46
  })
45
47
 
46
48
  process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {
47
49
  /* domainPublicRouter based nested-routers */
48
50
  domainPublicRouter.use(authSigninRouter.routes(), authSigninRouter.allowedMethods())
49
51
  domainPublicRouter.use(authSignupRouter.routes(), authSignupRouter.allowedMethods())
52
+ domainPublicRouter.use(webAuthnGlobalPublicRouter.routes(), webAuthnGlobalPublicRouter.allowedMethods())
50
53
 
51
54
  /* path '/admin/oauth/...' is deprecated. should use path '/oauth/...' for oauth2 related routing */
52
55
  domainPublicRouter.use('/oauth', oauth2Router.routes(), oauth2Router.allowedMethods()) // if i use context
@@ -61,11 +64,7 @@ process.on('bootstrap-module-domain-private-route' as any, (app, domainPrivateRo
61
64
  // pathBaseDomainRouter는 history-fallback의 경우에 인증 처리를 하기 위한 라우터이다.
62
65
  // (보통, URL 링크등을 통해서 domain path URL로 바로 요청하는 경우에 해당한다.)
63
66
  // pathBaseDomainRouter는 domain path를 domain-private-router를 사용하는 것을 전제로 한다.
64
- domainPrivateRouter.use(
65
- '/domain/:domain/oauth',
66
- oauth2AuthorizeRouter.routes(),
67
- oauth2AuthorizeRouter.allowedMethods()
68
- )
67
+ domainPrivateRouter.use('/domain/:domain/oauth', oauth2AuthorizeRouter.routes(), oauth2AuthorizeRouter.allowedMethods())
69
68
  domainPrivateRouter.use('/domain', pathBaseDomainRouter.routes(), pathBaseDomainRouter.allowedMethods())
70
69
  }
71
70
 
@@ -30,7 +30,8 @@ export class Appliance {
30
30
  readonly id: string
31
31
 
32
32
  @ManyToOne(type => Domain)
33
- domain: Domain
33
+ @Field(type => Domain)
34
+ domain?: Domain
34
35
 
35
36
  @RelationId((appliance: Appliance) => appliance.domain)
36
37
  domainId: string
@@ -71,8 +72,8 @@ export class Appliance {
71
72
  DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
72
73
  ? 'longtext'
73
74
  : DATABASE_TYPE == 'oracle'
74
- ? 'clob'
75
- : 'varchar'
75
+ ? 'clob'
76
+ : 'varchar'
76
77
  })
77
78
  @Field({ nullable: true })
78
79
  @Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
@@ -54,6 +54,7 @@ export class Application {
54
54
  readonly id?: string
55
55
 
56
56
  @ManyToOne(type => Domain)
57
+ @Field(type => Domain)
57
58
  domain?: Domain
58
59
 
59
60
  @RelationId((application: Application) => application.domain)
@@ -97,8 +98,8 @@ export class Application {
97
98
  DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
98
99
  ? 'longtext'
99
100
  : DATABASE_TYPE == 'oracle'
100
- ? 'clob'
101
- : 'varchar'
101
+ ? 'clob'
102
+ : 'varchar'
102
103
  })
103
104
  @Field({ nullable: true })
104
105
  @Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
@@ -113,8 +114,8 @@ export class Application {
113
114
  DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
114
115
  ? 'enum'
115
116
  : DATABASE_TYPE == 'oracle'
116
- ? 'varchar2'
117
- : 'smallint',
117
+ ? 'varchar2'
118
+ : 'smallint',
118
119
  enum: ApplicationType,
119
120
  default: ApplicationType.OTHERS
120
121
  })
@@ -18,4 +18,7 @@ export class AuthProviderParameterSpec {
18
18
 
19
19
  @Field(type => ScalarObject, { nullable: true })
20
20
  property?: { [key: string]: any }
21
+
22
+ @Field(type => ScalarObject, { nullable: true })
23
+ styles?: { [key: string]: any }
21
24
  }
@@ -1,11 +1,7 @@
1
- import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
2
- import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
3
- import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
1
+ import { ObjectType, Field, InputType, Int, ID } from 'type-graphql'
4
2
 
5
- import { ObjectRef, ScalarObject } from '@things-factory/shell'
6
-
7
- import { AuthProvider, AuthProviderStatus } from './auth-provider'
8
- import { AuthProviderParameterSpec } from './auth-provider-parameter-spec'
3
+ import { ScalarObject } from '@things-factory/shell'
4
+ import { AuthProvider } from './auth-provider'
9
5
 
10
6
  @InputType()
11
7
  export class NewAuthProvider {
@@ -1,17 +1,15 @@
1
1
  import {
2
2
  CreateDateColumn,
3
3
  UpdateDateColumn,
4
- DeleteDateColumn,
5
4
  Entity,
6
5
  Index,
7
6
  Column,
8
7
  RelationId,
9
8
  ManyToOne,
10
9
  OneToMany,
11
- PrimaryGeneratedColumn,
12
- VersionColumn
10
+ PrimaryGeneratedColumn
13
11
  } from 'typeorm'
14
- import { Directive, ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
12
+ import { Directive, ObjectType, Field, Int, ID } from 'type-graphql'
15
13
 
16
14
  import { Domain, ScalarObject, encryptTransformer } from '@things-factory/shell'
17
15
  import { User } from '../user/user'
@@ -30,16 +28,6 @@ export type AuthProviderRegistry = {
30
28
  [type: string]: AuthProviderImpl
31
29
  }
32
30
 
33
- export enum AuthProviderStatus {
34
- STATUS_A = 'STATUS_A',
35
- STATUS_B = 'STATUS_B'
36
- }
37
-
38
- registerEnumType(AuthProviderStatus, {
39
- name: 'AuthProviderStatus',
40
- description: 'state enumeration of a authProvider'
41
- })
42
-
43
31
  @ObjectType()
44
32
  export class AuthProviderType {
45
33
  @Field()
@@ -75,7 +63,7 @@ export class AuthProvider {
75
63
  readonly id: string
76
64
 
77
65
  @ManyToOne(type => Domain)
78
- @Field({ nullable: true })
66
+ @Field(type => Domain)
79
67
  domain?: Domain
80
68
 
81
69
  @RelationId((authProvider: AuthProvider) => authProvider.domain)
@@ -89,10 +77,6 @@ export class AuthProvider {
89
77
  @Field({ nullable: true })
90
78
  active?: boolean
91
79
 
92
- @Column({ nullable: true })
93
- @Field({ nullable: true })
94
- state?: AuthProviderStatus
95
-
96
80
  @Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
97
81
  @Column({ nullable: true })
98
82
  @Field({ nullable: true })
@@ -19,8 +19,8 @@ export class GrantedRole {
19
19
  roleId: string
20
20
 
21
21
  @ManyToOne(type => Domain)
22
- @Field()
23
- domain: Domain
22
+ @Field(type => Domain)
23
+ domain?: Domain
24
24
 
25
25
  @RelationId((grantedRole: GrantedRole) => grantedRole.domain)
26
26
  domainId: string
@@ -1,8 +1,5 @@
1
1
  /* IMPORT ENTITIES AND RESOLVERS */
2
- import {
3
- entities as UsersAuthProvidersEntities,
4
- resolvers as UsersAuthProvidersResolvers
5
- } from './users-auth-providers'
2
+ import { entities as UsersAuthProvidersEntities, resolvers as UsersAuthProvidersResolvers } from './users-auth-providers'
6
3
  import { entities as AuthProviderEntities, resolvers as AuthProviderResolvers } from './auth-provider'
7
4
  import { resolvers as AppbindingResolver } from './app-binding'
8
5
  import { entities as ApplianceEntities, resolvers as ApplianceResolvers } from './appliance'
@@ -18,6 +15,7 @@ import { privilegeDirectiveResolver, privilegeDirectiveTypeDefs } from './privil
18
15
  import { entities as RoleEntities, resolvers as RoleResolvers } from './role'
19
16
  import { entities as UserEntities, resolvers as UserResolvers } from './user'
20
17
  import { entities as VerificationTokenEntities } from './verification-token'
18
+ import { entities as WebAuthCredentialEntities } from './web-auth-credential'
21
19
 
22
20
  /* EXPORT ENTITY TYPES */
23
21
  export * from './users-auth-providers/users-auth-providers'
@@ -34,6 +32,7 @@ export * from './app-binding/app-binding'
34
32
  export * from './password-history/password-history'
35
33
  export * from './verification-token/verification-token'
36
34
  export * from './login-history/login-history'
35
+ export * from './web-auth-credential/web-auth-credential'
37
36
 
38
37
  /* EXPORT TYPES */
39
38
  export * from './app-binding/app-binding-types'
@@ -60,7 +59,8 @@ export const entities = [
60
59
  ...InvitationEntities,
61
60
  ...PasswordHistoryEntities,
62
61
  ...VerificationTokenEntities,
63
- ...LoginHistoryEntities
62
+ ...LoginHistoryEntities,
63
+ ...WebAuthCredentialEntities
64
64
  ]
65
65
 
66
66
  export const schema = {
@@ -14,14 +14,14 @@ export class LoginHistory {
14
14
  readonly id: string
15
15
 
16
16
  @ManyToOne(type => Domain)
17
- @Field()
18
- accessDomain: Domain
17
+ @Field(type => Domain)
18
+ accessDomain?: Domain
19
19
 
20
20
  @RelationId((loginHistory: LoginHistory) => loginHistory.accessDomain)
21
21
  accessDomainId: string
22
22
 
23
23
  @ManyToOne(type => User)
24
- @Field()
24
+ @Field(type => User)
25
25
  accessUser: User
26
26
 
27
27
  @RelationId((loginHistory: LoginHistory) => loginHistory.accessUser)
@@ -20,25 +20,25 @@ export class Partner {
20
20
  readonly id: string
21
21
 
22
22
  @ManyToOne(type => Domain)
23
- @Field()
24
- domain: Domain
23
+ @Field(type => Domain)
24
+ domain?: Domain
25
25
 
26
26
  @RelationId((partner: Partner) => partner.domain)
27
27
  domainId: string
28
28
 
29
29
  @ManyToOne(type => Domain)
30
- @Field()
31
- partnerDomain: Domain
30
+ @Field(type => Domain)
31
+ partnerDomain?: Domain
32
32
 
33
33
  @RelationId((partner: Partner) => partner.partnerDomain)
34
34
  partnerDomainId: string
35
35
 
36
36
  @CreateDateColumn()
37
- @Field()
37
+ @Field({ nullable: true })
38
38
  requestedAt: Date
39
39
 
40
40
  @UpdateDateColumn()
41
- @Field()
41
+ @Field({ nullable: true })
42
42
  approvedAt: Date
43
43
 
44
44
  @ManyToOne(type => User, { nullable: true })
@@ -24,13 +24,14 @@ export class Role {
24
24
  readonly id: string
25
25
 
26
26
  @ManyToOne(type => Domain)
27
- domain: Domain
27
+ @Field(type => Domain)
28
+ domain?: Domain
28
29
 
29
30
  @RelationId((role: Role) => role.domain)
30
31
  domainId: string
31
32
 
32
33
  @Column()
33
- @Field()
34
+ @Field({ nullable: true })
34
35
  name: string
35
36
 
36
37
  @ManyToMany(type => User, user => user.roles)