@things-factory/auth-base 7.0.1-rc.7 → 7.0.1-rc.8
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/dist-server/constants/error-code.d.ts +1 -1
- package/dist-server/constants/error-code.js +2 -2
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/middlewares/webauthn-middleware.d.ts +1 -2
- package/dist-server/middlewares/webauthn-middleware.js +73 -46
- package/dist-server/middlewares/webauthn-middleware.js.map +1 -1
- package/dist-server/router/webauthn-router.js +50 -26
- package/dist-server/router/webauthn-router.js.map +1 -1
- package/dist-server/service/web-auth-credential/web-auth-credential.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -8
- package/server/constants/error-code.ts +1 -1
- package/server/middlewares/webauthn-middleware.ts +101 -56
- package/server/router/webauthn-router.ts +70 -38
- package/server/service/web-auth-credential/web-auth-credential.ts +1 -0
- package/translations/en.json +3 -2
- package/translations/ja.json +3 -2
- package/translations/ko.json +3 -1
- package/translations/ms.json +3 -2
- package/translations/zh.json +4 -2
@@ -15,6 +15,6 @@ export declare const PASSWORD_PATTERN_NOT_MATCHED = "password should match the r
|
|
15
15
|
export declare const USER_DUPLICATED = "user duplicated";
|
16
16
|
export declare const PASSWORD_USED_PAST = "password used in the past";
|
17
17
|
export declare const VERIFICATION_ERROR = "user or verification token not found";
|
18
|
+
export declare const AUTHN_VERIFICATION_FAILED = "authn verification failed";
|
18
19
|
export declare const USER_CREDENTIAL_NOT_FOUND = "user credential not found";
|
19
20
|
export declare const AUTH_ERROR = "auth error";
|
20
|
-
export declare const FIDO2_CERT_UNSUPPORTED = "fido2 certificate unsupported";
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.AUTH_ERROR = exports.USER_CREDENTIAL_NOT_FOUND = exports.AUTHN_VERIFICATION_FAILED = exports.VERIFICATION_ERROR = exports.PASSWORD_USED_PAST = exports.USER_DUPLICATED = exports.PASSWORD_PATTERN_NOT_MATCHED = exports.CONFIRM_PASSWORD_NOT_MATCHED = exports.SUBDOMAIN_NOTFOUND = exports.AUTH_INVALID = exports.TOKEN_INVALID = exports.REDIRECT_TO_DEFAULT_DOMAIN = exports.NO_SELECTED_DOMAIN = exports.UNAVAILABLE_DOMAIN = exports.NO_AVAILABLE_DOMAIN = exports.USER_DELETED = exports.USER_LOCKED = exports.USER_NOT_ACTIVATED = exports.PASSWORD_NOT_MATCHED = exports.USER_NOT_FOUND = void 0;
|
4
4
|
exports.USER_NOT_FOUND = 'user not found';
|
5
5
|
exports.PASSWORD_NOT_MATCHED = 'password-not-matched';
|
6
6
|
exports.USER_NOT_ACTIVATED = 'user not activated';
|
@@ -18,7 +18,7 @@ exports.PASSWORD_PATTERN_NOT_MATCHED = 'password should match the rule';
|
|
18
18
|
exports.USER_DUPLICATED = 'user duplicated';
|
19
19
|
exports.PASSWORD_USED_PAST = 'password used in the past';
|
20
20
|
exports.VERIFICATION_ERROR = 'user or verification token not found';
|
21
|
+
exports.AUTHN_VERIFICATION_FAILED = 'authn verification failed';
|
21
22
|
exports.USER_CREDENTIAL_NOT_FOUND = 'user credential not found';
|
22
23
|
exports.AUTH_ERROR = 'auth error';
|
23
|
-
exports.FIDO2_CERT_UNSUPPORTED = 'fido2 certificate unsupported';
|
24
24
|
//# sourceMappingURL=error-code.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"error-code.js","sourceRoot":"","sources":["../../server/constants/error-code.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,gBAAgB,CAAA;AACjC,QAAA,oBAAoB,GAAG,sBAAsB,CAAA;AAC7C,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,WAAW,GAAG,aAAa,CAAA;AAC3B,QAAA,YAAY,GAAG,cAAc,CAAA;AAC7B,QAAA,mBAAmB,GAAG,qBAAqB,CAAA;AAC3C,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,0BAA0B,GAAG,4BAA4B,CAAA;AACzD,QAAA,aAAa,GAAG,eAAe,CAAA;AAC/B,QAAA,YAAY,GAAG,cAAc,CAAA;AAC7B,QAAA,kBAAkB,GAAG,qBAAqB,CAAA;AAC1C,QAAA,4BAA4B,GAAG,8BAA8B,CAAA;AAC7D,QAAA,4BAA4B,GAAG,gCAAgC,CAAA;AAC/D,QAAA,eAAe,GAAG,iBAAiB,CAAA;AACnC,QAAA,kBAAkB,GAAG,2BAA2B,CAAA;AAChD,QAAA,kBAAkB,GAAG,sCAAsC,CAAA;AAC3D,QAAA,yBAAyB,GAAG,2BAA2B,CAAA;AACvD,QAAA,
|
1
|
+
{"version":3,"file":"error-code.js","sourceRoot":"","sources":["../../server/constants/error-code.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,gBAAgB,CAAA;AACjC,QAAA,oBAAoB,GAAG,sBAAsB,CAAA;AAC7C,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,WAAW,GAAG,aAAa,CAAA;AAC3B,QAAA,YAAY,GAAG,cAAc,CAAA;AAC7B,QAAA,mBAAmB,GAAG,qBAAqB,CAAA;AAC3C,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,kBAAkB,GAAG,oBAAoB,CAAA;AACzC,QAAA,0BAA0B,GAAG,4BAA4B,CAAA;AACzD,QAAA,aAAa,GAAG,eAAe,CAAA;AAC/B,QAAA,YAAY,GAAG,cAAc,CAAA;AAC7B,QAAA,kBAAkB,GAAG,qBAAqB,CAAA;AAC1C,QAAA,4BAA4B,GAAG,8BAA8B,CAAA;AAC7D,QAAA,4BAA4B,GAAG,gCAAgC,CAAA;AAC/D,QAAA,eAAe,GAAG,iBAAiB,CAAA;AACnC,QAAA,kBAAkB,GAAG,2BAA2B,CAAA;AAChD,QAAA,kBAAkB,GAAG,sCAAsC,CAAA;AAC3D,QAAA,yBAAyB,GAAG,2BAA2B,CAAA;AACvD,QAAA,yBAAyB,GAAG,2BAA2B,CAAA;AACvD,QAAA,UAAU,GAAG,YAAY,CAAA","sourcesContent":["export const USER_NOT_FOUND = 'user not found'\nexport const PASSWORD_NOT_MATCHED = 'password-not-matched'\nexport const USER_NOT_ACTIVATED = 'user not activated'\nexport const USER_LOCKED = 'user-locked'\nexport const USER_DELETED = 'user-deleted'\nexport const NO_AVAILABLE_DOMAIN = 'no-available-domain'\nexport const UNAVAILABLE_DOMAIN = 'unavailable-domain'\nexport const NO_SELECTED_DOMAIN = 'no-selected-domain'\nexport const REDIRECT_TO_DEFAULT_DOMAIN = 'redirect-to-default-domain'\nexport const TOKEN_INVALID = 'token-invalid'\nexport const AUTH_INVALID = 'auth-invalid'\nexport const SUBDOMAIN_NOTFOUND = 'subdomain not found'\nexport const CONFIRM_PASSWORD_NOT_MATCHED = 'confirm password not matched'\nexport const PASSWORD_PATTERN_NOT_MATCHED = 'password should match the rule'\nexport const USER_DUPLICATED = 'user duplicated'\nexport const PASSWORD_USED_PAST = 'password used in the past'\nexport const VERIFICATION_ERROR = 'user or verification token not found'\nexport const AUTHN_VERIFICATION_FAILED = 'authn verification failed'\nexport const USER_CREDENTIAL_NOT_FOUND = 'user credential not found'\nexport const AUTH_ERROR = 'auth error'\n"]}
|
@@ -1,2 +1 @@
|
|
1
|
-
export declare
|
2
|
-
export declare function webAuthnMiddleware(context: any, next: any): Promise<any>;
|
1
|
+
export declare function createWebAuthnMiddleware(strategy: 'webauthn-register' | 'webauthn-login'): (context: any, next: any) => Promise<any>;
|
@@ -1,68 +1,95 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
4
|
-
exports.webAuthnMiddleware = webAuthnMiddleware;
|
3
|
+
exports.createWebAuthnMiddleware = createWebAuthnMiddleware;
|
5
4
|
const tslib_1 = require("tslib");
|
6
5
|
const koa_passport_1 = tslib_1.__importDefault(require("koa-passport"));
|
7
|
-
const
|
6
|
+
const passport_custom_1 = require("passport-custom");
|
8
7
|
const shell_1 = require("@things-factory/shell");
|
9
8
|
const user_1 = require("../service/user/user");
|
10
9
|
const auth_error_1 = require("../errors/auth-error");
|
11
10
|
const web_auth_credential_1 = require("../service/web-auth-credential/web-auth-credential");
|
12
|
-
|
13
|
-
koa_passport_1.default.use(new
|
14
|
-
const
|
15
|
-
|
16
|
-
|
11
|
+
const server_1 = require("@simplewebauthn/server");
|
12
|
+
koa_passport_1.default.use('webauthn-register', new passport_custom_1.Strategy(async (context, done) => {
|
13
|
+
const { body, session, user, hostname, origin } = context;
|
14
|
+
const challenge = session.challenge;
|
15
|
+
const verification = await (0, server_1.verifyRegistrationResponse)({
|
16
|
+
response: body,
|
17
|
+
expectedChallenge: challenge,
|
18
|
+
expectedOrigin: origin,
|
19
|
+
expectedRPID: hostname,
|
20
|
+
expectedType: 'webauthn.create',
|
21
|
+
requireUserVerification: false
|
22
|
+
});
|
23
|
+
if (verification.verified) {
|
24
|
+
const { registrationInfo } = verification;
|
25
|
+
const publicKey = Buffer.from(registrationInfo.credentialPublicKey).toString('base64');
|
26
|
+
if (user) {
|
27
|
+
const webAuthRepository = (0, shell_1.getRepository)(web_auth_credential_1.WebAuthCredential);
|
28
|
+
await webAuthRepository.save({
|
29
|
+
user,
|
30
|
+
credentialId: registrationInfo.credentialID,
|
31
|
+
publicKey,
|
32
|
+
counter: registrationInfo.counter,
|
33
|
+
creator: user,
|
34
|
+
updater: user
|
35
|
+
});
|
36
|
+
}
|
37
|
+
return done(null, user);
|
17
38
|
}
|
39
|
+
else {
|
40
|
+
return done(null, false);
|
41
|
+
}
|
42
|
+
}));
|
43
|
+
koa_passport_1.default.use('webauthn-login', new passport_custom_1.Strategy(async (context, done) => {
|
44
|
+
const { body, session, origin, hostname } = context;
|
45
|
+
const challenge = session.challenge;
|
46
|
+
const assertionResponse = body;
|
18
47
|
const credential = await (0, shell_1.getRepository)(web_auth_credential_1.WebAuthCredential).findOne({
|
19
|
-
where: {
|
48
|
+
where: {
|
49
|
+
credentialId: assertionResponse.id
|
50
|
+
}
|
20
51
|
});
|
21
52
|
if (!credential) {
|
22
|
-
return
|
23
|
-
}
|
24
|
-
try {
|
25
|
-
return cb(null, user, credential.publicKey);
|
53
|
+
return done(null, false);
|
26
54
|
}
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
55
|
+
const verification = await (0, server_1.verifyAuthenticationResponse)({
|
56
|
+
response: body,
|
57
|
+
expectedChallenge: challenge,
|
58
|
+
expectedOrigin: origin,
|
59
|
+
expectedRPID: hostname,
|
60
|
+
requireUserVerification: false,
|
61
|
+
authenticator: {
|
62
|
+
credentialID: credential.credentialId,
|
63
|
+
credentialPublicKey: new Uint8Array(Buffer.from(credential.publicKey, 'base64')),
|
64
|
+
counter: credential.counter
|
65
|
+
}
|
36
66
|
});
|
37
|
-
|
38
|
-
|
39
|
-
|
67
|
+
if (verification.verified) {
|
68
|
+
const { authenticationInfo } = verification;
|
69
|
+
credential.counter = authenticationInfo.newCounter;
|
70
|
+
await (0, shell_1.getRepository)(web_auth_credential_1.WebAuthCredential).save(credential);
|
71
|
+
const user = await (0, shell_1.getRepository)(user_1.User).findOne({ where: { email: body.email } });
|
72
|
+
return done(null, user);
|
73
|
+
}
|
74
|
+
else {
|
75
|
+
return done(verification, false);
|
40
76
|
}
|
41
|
-
await webAuthRepository.save({
|
42
|
-
user: userObject,
|
43
|
-
credentialId: id,
|
44
|
-
publicKey,
|
45
|
-
counter: 0
|
46
|
-
});
|
47
|
-
return cb(null, userObject);
|
48
77
|
}));
|
49
|
-
|
50
|
-
return
|
51
|
-
|
52
|
-
if (
|
53
|
-
throw new auth_error_1.AuthError(info);
|
54
|
-
}
|
55
|
-
else {
|
78
|
+
function createWebAuthnMiddleware(strategy) {
|
79
|
+
return async function webAuthnMiddleware(context, next) {
|
80
|
+
return koa_passport_1.default.authenticate(strategy, { session: true, failureMessage: true, failWithError: true }, async (err, user) => {
|
81
|
+
if (err || !user) {
|
56
82
|
throw new auth_error_1.AuthError({
|
57
|
-
errorCode: auth_error_1.AuthError.ERROR_CODES.
|
58
|
-
detail:
|
83
|
+
errorCode: auth_error_1.AuthError.ERROR_CODES.AUTHN_VERIFICATION_FAILED,
|
84
|
+
detail: err
|
59
85
|
});
|
60
86
|
}
|
61
|
-
|
62
|
-
|
63
|
-
|
87
|
+
else {
|
88
|
+
context.state.user = user;
|
89
|
+
context.body = { user, verified: true };
|
90
|
+
}
|
64
91
|
await next();
|
65
|
-
}
|
66
|
-
}
|
92
|
+
})(context, next);
|
93
|
+
};
|
67
94
|
}
|
68
95
|
//# sourceMappingURL=webauthn-middleware.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webauthn-middleware.js","sourceRoot":"","sources":["../../server/middlewares/webauthn-middleware.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"webauthn-middleware.js","sourceRoot":"","sources":["../../server/middlewares/webauthn-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import passport from 'koa-passport'\nimport { Strategy as CustomStrategy } from 'passport-custom'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { User } from '../service/user/user'\nimport { AuthError } from '../errors/auth-error'\n\nimport { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential'\nimport {\n verifyRegistrationResponse,\n verifyAuthenticationResponse\n} from '@simplewebauthn/server'\n\nimport { AuthenticatorAssertionResponse } from '@simplewebauthn/types'\n\npassport.use(\n 'webauthn-register',\n new CustomStrategy(async (context, done) => {\n const { body, session, user, hostname, origin } = context as any\n\n const challenge = session.challenge\n\n const verification = await verifyRegistrationResponse({\n response: body,\n expectedChallenge: challenge,\n expectedOrigin: origin,\n expectedRPID: hostname,\n expectedType: 'webauthn.create',\n requireUserVerification: false\n })\n\n if (verification.verified) {\n const { registrationInfo } = verification\n const publicKey = Buffer.from(registrationInfo.credentialPublicKey).toString('base64');\n\n if (user) {\n const webAuthRepository = getRepository(WebAuthCredential)\n await webAuthRepository.save({\n user,\n credentialId: registrationInfo.credentialID,\n publicKey,\n counter: registrationInfo.counter,\n creator: user,\n updater: user\n })\n }\n\n return done(null, user)\n } else {\n return done(null, false)\n }\n })\n)\n\npassport.use(\n 'webauthn-login',\n new CustomStrategy(async (context, done) => {\n const { body, session, origin, hostname } = context as any\n\n const challenge = session.challenge\n\n const assertionResponse = body as {\n id: string\n response: AuthenticatorAssertionResponse\n }\n \n const credential = await getRepository(WebAuthCredential).findOne({\n where: {\n credentialId: assertionResponse.id\n }\n })\n\n if (!credential) {\n return done(null, false)\n }\n\n const verification = await verifyAuthenticationResponse({\n response: body,\n expectedChallenge: challenge,\n expectedOrigin: origin,\n expectedRPID: hostname,\n requireUserVerification: false,\n authenticator: {\n credentialID: credential.credentialId,\n credentialPublicKey: new Uint8Array(Buffer.from(credential.publicKey, 'base64')),\n counter: credential.counter\n }\n })\n\n if (verification.verified) {\n const { authenticationInfo } = verification\n credential.counter = authenticationInfo.newCounter\n await getRepository(WebAuthCredential).save(credential)\n\n const user = await getRepository(User).findOne({ where: { email: body.email } })\n return done(null, user)\n } else {\n return done(verification, false)\n }\n })\n)\n\nexport function createWebAuthnMiddleware(strategy: 'webauthn-register' | 'webauthn-login') {\n return async function webAuthnMiddleware(context, next) {\n return passport.authenticate(\n strategy,\n { session: true, failureMessage: true, failWithError: true },\n async (err, user) => {\n if (err || !user) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.AUTHN_VERIFICATION_FAILED,\n detail: err\n });\n } else {\n context.state.user = user;\n\n context.body = { user, verified: true };\n }\n\n await next();\n }\n )(context, next);\n };\n}\n"]}
|
@@ -2,20 +2,60 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.webAuthnGlobalPrivateRouter = exports.webAuthnGlobalPublicRouter = void 0;
|
4
4
|
const tslib_1 = require("tslib");
|
5
|
-
const util_1 = tslib_1.__importDefault(require("util"));
|
6
5
|
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
7
|
+
const env_1 = require("@things-factory/env");
|
8
|
+
const server_1 = require("@simplewebauthn/server");
|
9
|
+
const web_auth_credential_1 = require("../service/web-auth-credential/web-auth-credential");
|
7
10
|
const access_token_cookie_1 = require("../utils/access-token-cookie");
|
8
11
|
const webauthn_middleware_1 = require("../middlewares/webauthn-middleware");
|
9
12
|
exports.webAuthnGlobalPublicRouter = new koa_router_1.default();
|
10
13
|
exports.webAuthnGlobalPrivateRouter = new koa_router_1.default();
|
11
|
-
const
|
12
|
-
exports.
|
13
|
-
const
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
const { name: rpName } = env_1.appPackage;
|
15
|
+
exports.webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context, next) => {
|
16
|
+
const { user } = context.state;
|
17
|
+
const rpID = context.hostname;
|
18
|
+
const webAuthCredentials = await (0, shell_1.getRepository)(web_auth_credential_1.WebAuthCredential).find({
|
19
|
+
where: {
|
20
|
+
user: { id: user.id }
|
21
|
+
}
|
22
|
+
});
|
23
|
+
const options = await (0, server_1.generateRegistrationOptions)({
|
24
|
+
rpName,
|
25
|
+
rpID,
|
26
|
+
userName: user.email,
|
27
|
+
userDisplayName: user.name,
|
28
|
+
// Don't prompt users for additional information about the authenticator
|
29
|
+
// (Recommended for smoother UX)
|
30
|
+
attestationType: 'none',
|
31
|
+
// Prevent users from re-registering existing authenticators
|
32
|
+
excludeCredentials: webAuthCredentials.map(credential => ({
|
33
|
+
id: credential.credentialId
|
34
|
+
// Optional
|
35
|
+
// transports: credential.transports
|
36
|
+
})),
|
37
|
+
authenticatorSelection: {
|
38
|
+
// Defaults
|
39
|
+
residentKey: 'preferred',
|
40
|
+
userVerification: 'preferred',
|
41
|
+
// Optional
|
42
|
+
authenticatorAttachment: 'platform'
|
43
|
+
}
|
44
|
+
});
|
45
|
+
context.session.challenge = options.challenge;
|
46
|
+
context.body = options;
|
17
47
|
});
|
18
|
-
exports.
|
48
|
+
exports.webAuthnGlobalPrivateRouter.post('/auth/verify-registration', (0, webauthn_middleware_1.createWebAuthnMiddleware)('webauthn-register'));
|
49
|
+
exports.webAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context, next) => {
|
50
|
+
const rpID = context.hostname;
|
51
|
+
const options = await (0, server_1.generateAuthenticationOptions)({
|
52
|
+
rpID,
|
53
|
+
userVerification: 'preferred'
|
54
|
+
});
|
55
|
+
context.session.challenge = options.challenge;
|
56
|
+
context.body = options;
|
57
|
+
});
|
58
|
+
exports.webAuthnGlobalPublicRouter.post('/auth/signin-webauthn', (0, webauthn_middleware_1.createWebAuthnMiddleware)('webauthn-login'), async (context, next) => {
|
19
59
|
const { domain, user } = context.state;
|
20
60
|
const { request } = context;
|
21
61
|
const { body: reqBody } = request;
|
@@ -23,23 +63,7 @@ exports.webAuthnGlobalPublicRouter.post('/auth/signin-webauthn', webauthn_middle
|
|
23
63
|
(0, access_token_cookie_1.setAccessTokenCookie)(context, token);
|
24
64
|
var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`;
|
25
65
|
/* 2단계 인터렉션 때문에 브라우저에서 fetch(...)로 진행될 것이므로, redirect(3xx) 응답으로 처리할 수 없다. 따라서, 데이타로 redirectURL를 응답한다. */
|
26
|
-
context.body = { redirectURL };
|
27
|
-
|
28
|
-
exports.webAuthnGlobalPrivateRouter.post('/auth/register-webauthn/challenge', async (context, next) => {
|
29
|
-
const { user } = context.state;
|
30
|
-
const { id, name } = user || {};
|
31
|
-
const challenge = await challengeAsync(Object.assign(Object.assign({}, context.request), { session: context.session }), {
|
32
|
-
user: {
|
33
|
-
id
|
34
|
-
}
|
35
|
-
});
|
36
|
-
context.body = {
|
37
|
-
user: {
|
38
|
-
id: Buffer.from(id).toString('base64'),
|
39
|
-
name: name,
|
40
|
-
displayName: name
|
41
|
-
},
|
42
|
-
challenge: Buffer.from(challenge).toString('base64')
|
43
|
-
};
|
66
|
+
context.body = { redirectURL, verified: true };
|
67
|
+
await next();
|
44
68
|
});
|
45
69
|
//# sourceMappingURL=webauthn-router.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"webauthn-router.js","sourceRoot":"","sources":["../../server/router/webauthn-router.ts"],"names":[],"mappings":";;;;AAAA,
|
1
|
+
{"version":3,"file":"webauthn-router.js","sourceRoot":"","sources":["../../server/router/webauthn-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\nimport { getRepository } from '@things-factory/shell'\nimport { appPackage } from '@things-factory/env'\n\nimport { generateRegistrationOptions, generateAuthenticationOptions } from '@simplewebauthn/server'\n\nimport { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential'\nimport {\n PublicKeyCredentialCreationOptionsJSON,\n} from '@simplewebauthn/server/script/deps'\nimport { setAccessTokenCookie } from '../utils/access-token-cookie'\nimport { createWebAuthnMiddleware } from '../middlewares/webauthn-middleware';\n\nexport const webAuthnGlobalPublicRouter = new Router()\nexport const webAuthnGlobalPrivateRouter = new Router()\n\nconst { name: rpName } = appPackage as any\n\nwebAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context, next) => {\n const { user } = context.state\n const rpID = context.hostname\n\n const webAuthCredentials = await getRepository(WebAuthCredential).find({\n where: {\n user: { id: user.id }\n }\n })\n\n const options: PublicKeyCredentialCreationOptionsJSON = await generateRegistrationOptions({\n rpName,\n rpID,\n userName: user.email,\n userDisplayName: user.name,\n // Don't prompt users for additional information about the authenticator\n // (Recommended for smoother UX)\n attestationType: 'none',\n // Prevent users from re-registering existing authenticators\n excludeCredentials: webAuthCredentials.map(credential => ({\n id: credential.credentialId\n // Optional\n // transports: credential.transports\n })),\n authenticatorSelection: {\n // Defaults\n residentKey: 'preferred',\n userVerification: 'preferred',\n // Optional\n authenticatorAttachment: 'platform'\n }\n })\n\n context.session.challenge = options.challenge\n context.body = options\n})\n\nwebAuthnGlobalPrivateRouter.post('/auth/verify-registration', createWebAuthnMiddleware('webauthn-register'));\n\nwebAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context, next) => {\n const rpID = context.hostname\n\n const options = await generateAuthenticationOptions({\n rpID,\n userVerification: 'preferred'\n })\n\n context.session.challenge = options.challenge\n context.body = options\n})\n\nwebAuthnGlobalPublicRouter.post(\n '/auth/signin-webauthn', createWebAuthnMiddleware('webauthn-login'),\n async (context, next) => {\n const { domain, user } = context. state\n const { request } = context\n const { body: reqBody } = request\n\n const token = await user.sign({ subdomain: domain?.subdomain })\n setAccessTokenCookie(context, token)\n\n var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`\n\n /* 2단계 인터렉션 때문에 브라우저에서 fetch(...)로 진행될 것이므로, redirect(3xx) 응답으로 처리할 수 없다. 따라서, 데이타로 redirectURL를 응답한다. */\n context.body = { redirectURL, verified: true }\n\n await next();\n }\n)\n"]}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"web-auth-credential.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/web-auth-credential.ts"],"names":[],"mappings":";;;;AAAA,+CAAwC;AACxC,qCASgB;AAEhB,uCAAmC;
|
1
|
+
{"version":3,"file":"web-auth-credential.js","sourceRoot":"","sources":["../../../server/service/web-auth-credential/web-auth-credential.ts"],"names":[],"mappings":";;;;AAAA,+CAAwC;AACxC,qCASgB;AAEhB,uCAAmC;AAS5B,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CA6C7B,CAAA;AA7CY,8CAAiB;AAGnB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;6CACC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACjC,WAAI;+CAAA;AAGX;IADC,IAAA,oBAAU,EAAC,CAAC,iBAAoC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC;;iDAC9D;AAIf;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uDACN;AAIpB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oDACT;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACX;AAIf;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;oDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;oDAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,WAAI;kDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,iBAAoC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC;;oDAC9D;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,WAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,WAAI;kDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,iBAAoC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC;;oDAC9D;4BA5CP,iBAAiB;IAN7B,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EACJ,0BAA0B,EAC1B,CAAC,iBAAoC,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,CAAC,YAAY,CAAC,EAClG,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB;GACY,iBAAiB,CA6C7B","sourcesContent":["import { Field, ID } from 'type-graphql'\nimport {\n CreateDateColumn,\n UpdateDateColumn,\n Entity,\n Index,\n Column,\n RelationId,\n ManyToOne,\n PrimaryGeneratedColumn\n} from 'typeorm'\n\nimport { User } from '../user/user'\nimport { AuthenticatorTransportFuture } from '@simplewebauthn/server/script/deps'\n\n@Entity()\n@Index(\n 'ix_web_auth_credential_0',\n (webAuthCredential: WebAuthCredential) => [webAuthCredential.user, webAuthCredential.credentialId],\n { unique: true }\n)\nexport class WebAuthCredential {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n user?: User\n\n @RelationId((webAuthCredential: WebAuthCredential) => webAuthCredential.user)\n userId?: string\n\n @Column()\n @Field({ nullable: true })\n credentialId: string\n\n @Column()\n @Field({ nullable: true })\n publicKey: string\n\n @Column()\n @Field({ nullable: true })\n counter: number\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((webAuthCredential: WebAuthCredential) => webAuthCredential.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((webAuthCredential: WebAuthCredential) => webAuthCredential.updater)\n updaterId?: string\n}\n"]}
|