Please check my HTTP login Flow

It implements a login function that authenticates users compared to the value of the database.
When I run node red and enter id/pw for the first time, it works fine. Compare the values with DB to move on only when they match. However, I come back to the login screen and leave the input empty and just press the login button to move on to the next screen again. (As if I succeeded in logging in.) I wonder where the hell the problem is.
I think it's a problem with the global variable that you set as the input not being initialized, or there's an error in the logic itself.
Please help me.

[
    {
        "id": "75ce2ad0555f4266",
        "type": "http in",
        "z": "b1537c574e27a2f9",
        "name": "",
        "url": "/login_action",
        "method": "post",
        "upload": false,
        "swaggerDoc": "",
        "x": 210,
        "y": 580,
        "wires": [
            [
                "ad388a18420d9f62"
            ]
        ]
    },
    {
        "id": "ad388a18420d9f62",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "์ž…๋ ฅํ•œ value์— ๋Œ€ํ•ด set global",
        "func": "if (msg.payload.userID != \"\" && msg.payload.password != \"\") {\n    global.set(\"loginUserID\", msg.payload.userID);\n    global.set(\"loginPassword\", msg.payload.password);\n    //global.set(\"msg_login_action\",msg);\n} \nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 450,
        "y": 580,
        "wires": [
            [
                "0a3dc9fe4f6967a6"
            ]
        ]
    },
    {
        "id": "0a3dc9fe4f6967a6",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "์ž…๋ ฅํ•œ Username์ด mySQL์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ",
        "func": "var id = global.get(\"loginUserID\");\nvar password = global.get(\"loginPassword\");\nmsg.topic = \"SELECT * FROM users WHERE userID = ?\";\nif (id && password) {\n    msg.payload = [id];\n} else {\n    // ID๋‚˜ Password๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ, ๋นˆ ๋ฐฐ์—ด ๋ฐ˜ํ™˜\n    msg.payload = [];\n}\n//msg.topic = \"SELECT * FROM users WHERE userName = ? AND password = ?\";\n//msg.payload = [id];\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 780,
        "y": 580,
        "wires": [
            [
                "442a018c69b061ef"
            ]
        ]
    },
    {
        "id": "442a018c69b061ef",
        "type": "mysql",
        "z": "b1537c574e27a2f9",
        "mydb": "23139c6750c1e4b8",
        "name": "",
        "x": 290,
        "y": 640,
        "wires": [
            [
                "45720baf77e5d803"
            ]
        ]
    },
    {
        "id": "45720baf77e5d803",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "Authentication",
        "func": "/*if(msg.payload!=null){\n    msg.payload=\"Login Success\";\n}else {\n    msg.payload =\"Login Failed\";\n}\nreturn msg;*/\n// ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ํ•จ์ˆ˜\nvar loginUserID = global.get(\"loginUserID\");\nvar loginPassword = global.get(\"loginPassword\");\n\n\nif (msg.payload.length > 0) {\n    var userID = msg.payload[0].userID;\n    var userPassword = msg.payload[0].password;\n     if (userPassword === loginPassword) { // ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋น„๊ต\n\n         var encodedUserID = Buffer.from(userID).toString('base64');\n        msg.cookies = {\n            COOKIE: {\n                value: encodedUserID,\n                maxAge: 900000 // ์ฟ ํ‚ค ์œ ํšจ ์‹œ๊ฐ„ (๋ฐ€๋ฆฌ์ดˆ), ์—ฌ๊ธฐ์„œ๋Š” 15๋ถ„\n            }\n        };\n        msg.redirect = \"http://localhost:1880/ui\"; // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ๋Œ€์‹œ๋ณด๋“œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ\n    } else {\n         msg.payload = \"2\";\n    }\n} else {\n    msg.payload = \"1\";\n}\n\n\nreturn msg;\n\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 640,
        "wires": [
            [
                "91d7e94b135bbb57"
            ]
        ]
    },
    {
        "id": "91d7e94b135bbb57",
        "type": "switch",
        "z": "b1537c574e27a2f9",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "else"
            },
            {
                "t": "eq",
                "v": "1",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "2",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 3,
        "x": 590,
        "y": 640,
        "wires": [
            [
                "1021190647257c4d"
            ],
            [
                "ddbd12536354d9fa"
            ],
            [
                "2aaf8130381ccb06"
            ]
        ]
    },
    {
        "id": "ddbd12536354d9fa",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "= 1 : Unknown User",
        "func": "msg.payload = \"Login Failed: User Not Found.\";\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 770,
        "y": 660,
        "wires": [
            [
                "665ed3a23ab05c5f"
            ]
        ]
    },
    {
        "id": "2aaf8130381ccb06",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "= 2 : Wrong Password",
        "func": "msg.payload = \"Login Failed: Invalid Password.\";\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 760,
        "y": 700,
        "wires": [
            [
                "665ed3a23ab05c5f"
            ]
        ]
    },
    {
        "id": "665ed3a23ab05c5f",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "Login Failed",
        "func": "var msg1 = msg.payload;\nvar msg=global.get(\"msg_login_action\") || \"\";\nmsg.payload = msg1;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 950,
        "y": 680,
        "wires": [
            [
                "f06841e5dc3d9282"
            ]
        ]
    },
    {
        "id": "f06841e5dc3d9282",
        "type": "template",
        "z": "b1537c574e27a2f9",
        "name": "page",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<script>\n    // ์›น ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋  ๋•Œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜\n    window.onload = function() {\n        // ์„œ๋ฒ„์—์„œ ์ „๋‹ฌ๋ฐ›์€ ๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ์˜ด\n        var message = \"{{payload}}\"; // ์ด ๋ถ€๋ถ„์— Node-RED์—์„œ ์ „๋‹ฌํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋„ฃ์–ด์•ผ ํ•จ\n\n        // ๋กœ๊ทธ์ธ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ํŒ์—… ์ฐฝ์„ ํ‘œ์‹œ\n        if (message.includes(\"Login Failed\")) {\n            // ๋กœ๊ทธ์ธ ์‹คํŒจ ์‹œ ํŒ์—… ์ฐฝ ํ‘œ์‹œ\n            alert(message);\n            \n            // ํŒ์—… ํ™•์ธ ํ›„ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ž๋™ ์ด๋™\n            window.location.href = \"http://192.168.0.18:1880/login\"; // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ URL๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ\n        }\n    };\n</script>\n\n",
        "output": "str",
        "x": 1090,
        "y": 680,
        "wires": [
            [
                "08750f2d966af0ce"
            ]
        ]
    },
    {
        "id": "1021190647257c4d",
        "type": "template",
        "z": "b1537c574e27a2f9",
        "name": "page",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<script>\nwindow.location.href = \"http://192.168.0.18:1880/ui\";\n</script>",
        "output": "str",
        "x": 950,
        "y": 640,
        "wires": [
            [
                "50ef68fa45d8e864",
                "c913d0c7d0a84945"
            ]
        ]
    },
    {
        "id": "50ef68fa45d8e864",
        "type": "http response",
        "z": "b1537c574e27a2f9",
        "name": "Go to Dashboard",
        "statusCode": "",
        "headers": {},
        "x": 1130,
        "y": 620,
        "wires": []
    },
    {
        "id": "08750f2d966af0ce",
        "type": "http response",
        "z": "b1537c574e27a2f9",
        "name": "Return to Login",
        "statusCode": "",
        "headers": {},
        "x": 1240,
        "y": 680,
        "wires": []
    },
    {
        "id": "7037a375ba2d0cc2",
        "type": "http in",
        "z": "b1537c574e27a2f9",
        "name": "",
        "url": "/login",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 180,
        "y": 540,
        "wires": [
            [
                "39dd5592efadfa8d"
            ]
        ]
    },
    {
        "id": "39dd5592efadfa8d",
        "type": "template",
        "z": "b1537c574e27a2f9",
        "name": "<style>",
        "field": "payload.style",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "html {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    font-size: 150%;\n    height: 70vh; /* ๋ณด๋Š” ์‚ฌ๋žŒ์˜ ํŽ˜์ด์ง€์˜ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋จ. */\n}\n\nbody {\n    font-size: 14px;\n    font-family: 'Roboto', sans-serif;\n    background-color: #023047;\n}\n\n.logo-wrapper {\n    text-align: center;\n    margin-bottom: 20px;\n}\n\n.logo {\n    width: 200px; /* ๋กœ๊ณ  ์ด๋ฏธ์ง€์˜ ๋„ˆ๋น„๋ฅผ ์กฐ์ ˆ */\n    display: inline-block; /* ๋กœ๊ณ ๋ฅผ ๊ฐ€๋กœ ์ค‘์•™ ์ •๋ ฌํ•˜๊ธฐ ์œ„ํ•ด inline-block ์†์„ฑ ์ ์šฉ */\n\n}\n.logo img {\n    width: 100%; /* ๋กœ๊ณ  ์ด๋ฏธ์ง€์˜ ๋„ˆ๋น„๋ฅผ ๋ถ€๋ชจ ์š”์†Œ์— ๋งž๊ฒŒ ์กฐ์ ˆ */\n    height: auto; /* ์ด๋ฏธ์ง€ ๋น„์œจ ์œ ์ง€ */\n}\n\n.login-wrapper {\n    text-align: center; /* ๋ถ€๋ชจ ์š”์†Œ์— ๋Œ€ํ•ด ๊ฐ€์šด๋ฐ ์ •๋ ฌ์„ ์ ์šฉ */\n    margin-bottom: 20px;\n    width: 400px;\n    padding: 40px;\n    box-sizing: border-box;\n    background-color: #fff; /* ํฐ ๋ฐฐ๊ฒฝ ์ถ”๊ฐ€ */\n    border-radius: 10px; /* ๋ชจ์„œ๋ฆฌ๋ฅผ ๋‘ฅ๊ธ€๊ฒŒ ๋งŒ๋“ฆ */\n    box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.1); /* ๊ทธ๋ฆผ์ž ํšจ๊ณผ ์ถ”๊ฐ€ */\n    display: flex; /* ์ž์‹ ์š”์†Œ๋ฅผ ๊ฐ€๋กœ๋กœ ๋ฐฐ์น˜ํ•˜๊ธฐ ์œ„ํ•ด flex ์†์„ฑ ์ ์šฉ */\n    flex-direction: column; /* ์ž์‹ ์š”์†Œ๋ฅผ ์„ธ๋กœ๋กœ ๋ฐฐ์น˜ */\n    align-items: center; /* ์ž์‹ ์š”์†Œ๋ฅผ ๊ฐ€์šด๋ฐ ์ •๋ ฌ */\n}\n\n#login-form {\n    width: 100%; /* ํผ์˜ ๋„ˆ๋น„๋ฅผ 100%๋กœ ์„ค์ • */\n    margin-bottom: 20px; /* ํ•˜๋‹จ ์—ฌ๋ฐฑ ์ถ”๊ฐ€ */\n}\n#login-form > input {\n    width: 100%;\n    height: 48px;\n    padding: 0 10px;\n    box-sizing: border-box;\n    margin-bottom: 10px;\n    border-radius: 6px;\n    background-color: #F8F8F8;\n    border: 2px solid #023047; /* ๋ชจ๋“  ํ…Œ๋‘๋ฆฌ์— ๋Œ€ํ•œ ์ƒ‰์ƒ ์„ค์ • */\n    font-family: 'Lucida Sans', sans-serif; /* ์›ํ•˜๋Š” ํฐํŠธ๋กœ ๋ณ€๊ฒฝ */\n\n}\n\n#login-form > input::placeholder {\n    color: #D2D2D2;\n}\n\n#login-form > input[type=\"submit\"] {\n    color: #fff;\n    font-size: 16px;\n    background-color: #FFB703;\n    margin-top: 20px;\n    font-family: 'Lucida Sans', sans-serif; /* ์›ํ•˜๋Š” ํฐํŠธ๋กœ ๋ณ€๊ฒฝ */\n    border: none; /* ํ…Œ๋‘๋ฆฌ ์ œ๊ฑฐ */\n    width: 100%; /* ๋ฒ„ํŠผ์˜ ๋„ˆ๋น„๋ฅผ 100%๋กœ ์„ค์ •ํ•˜์—ฌ ํผ๊ณผ ๋™์ผํ•œ ๋„ˆ๋น„๋ฅผ ๊ฐ€์ง€๋„๋ก ํ•จ */\n}\n\n.submit-btn {\n    color: #fff; \n    font-size: 16px;\n    margin-top: 2px;\n    background-color: #FFB703; \n    border: 2px solid #FFB703; /* ํ™ฉ์ƒ‰ ํ…Œ๋‘๋ฆฌ */\n    border-radius: 6px; /* ๋ชจ์„œ๋ฆฌ๋ฅผ ๋‘ฅ๊ธ€๊ฒŒ ๋งŒ๋“ฆ */\n    //padding: 12px 0; /* ์œ„์•„๋ž˜ 12px, ์ขŒ์šฐ 0 */\n    width: 100%; /* ๋ฒ„ํŠผ์˜ ๋„ˆ๋น„๋ฅผ 100%๋กœ ์„ค์ •ํ•˜์—ฌ ํผ๊ณผ ๋™์ผํ•œ ๋„ˆ๋น„๋ฅผ ๊ฐ€์ง€๋„๋ก ํ•จ */\n    height: 48px;\n    cursor: pointer; /* ์ปค์„œ ๋ชจ์–‘์„ ํฌ์ธํ„ฐ๋กœ ๋ณ€๊ฒฝ */\n    font-family: 'Lucida Sans', sans-serif; /* ์›ํ•˜๋Š” ํฐํŠธ๋กœ ๋ณ€๊ฒฝ */\n}\n\n.submit-btn:hover {\n    background-color: #ffa500; /* ํ˜ธ๋ฒ„ ์‹œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€๊ฒฝ */\n}\n\n#login-form > input[type=\"checkbox\"] {\n    display: none;\n}\n\n#login-form > label {\n    color: #999999;\n}\n\n#login-form input[type=\"checkbox\"] + label {\n    cursor: pointer;\n    padding-left: 26px;\n    background-image: url(\"checkbox.png\");\n    background-repeat: no-repeat;\n    background-size: contain;\n    font-family: 'Lucida Sans', sans-serif; /* ์›ํ•˜๋Š” ํฐํŠธ๋กœ ๋ณ€๊ฒฝ */\n\n}\n\n#login-form input[type=\"checkbox\"]:checked + label {\n    background-image: url(\"checkbox-active.png\");\n    background-repeat: no-repeat;\n    background-size: contain;\n    font-family: 'Lucida Sans', sans-serif; /* ์›ํ•˜๋Š” ํฐํŠธ๋กœ ๋ณ€๊ฒฝ */\n}\n/* ###### 5. direction ###### */\n.direction__container {\n    width: 100%;\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n}\n.direction__container p {\n    text-align: center;\n    margin-top: 10px;\n}",
        "output": "str",
        "x": 560,
        "y": 540,
        "wires": [
            [
                "ae97fec1521bf021"
            ]
        ]
    },
    {
        "id": "ae97fec1521bf021",
        "type": "template",
        "z": "b1537c574e27a2f9",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<!DOCTYPE html>\n<html>\n<head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<style> {{{payload.style}}} </style>\n</head>\n<body>\n    <div class=\"login-wrapper\">\n        <div class=\"logo-wrapper\">\n            <div class=\"logo\">\n                <img src=\"http://theconsol.co.kr/public/img/logo/logo.png\">\n            </div>\n        </div>\n        <form method=\"post\" action=\"http://192.168.0.18:1880/login_action\" id=\"login-form\">\n            <input type=\"text\" name=\"userID\" placeholder=\"ID\">\n            <input type=\"password\" name=\"password\" placeholder=\"Password\">\n            <label for=\"remember-check\">\n                <input type=\"checkbox\" id=\"remember-check\">์•„์ด๋”” ์ €์žฅํ•˜๊ธฐ\n            </label>\n            <input type=\"submit\"  id=\"login-button\" value=\"Login\">\n        </form>\n        <button class=\"submit-btn\" onclick=\"goToSignUpPage()\">Sign up</button>   \n    </div>\n<script>\n    document.addEventListener(\"DOMContentLoaded\", function() {\n        console.log(\"JavaScript ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\");\n        // ํผ ์ œ์ถœ ์‹œ ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๋น„์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋น„์–ด ์žˆ์œผ๋ฉด ์ œ์ถœ์„ ๋ง‰์Œ\n        document.getElementById(\"login-form\").addEventListener(\"submit\", function(event) {\n            if (useridInput.value.trim() === \"\" || passwordInput.value.trim() === \"\") {\n                // ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ํผ ์ œ์ถœ์„ ๋ง‰์Œ\n                event.preventDefault();\n                // ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ (์˜ˆ๋ฅผ ๋“ค์–ด alert ์‚ฌ์šฉ)\n                alert(\"ID์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.\");\n            }\n        });\n        // Sign up ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ํ•จ์ˆ˜\n        function goToSignUpPage() {\n            console.log(\"Sign up ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.\");\n            window.location.href = \"http://192.168.0.18:1880/signup\"; // \"/signup\"์œผ๋กœ ๋ณ€๊ฒฝ\n        }\n        \n        // ํ•„์ˆ˜ ์ž…๋ ฅ ํ•„๋“œ\n        var useridInput = document.getElementById(\"userID\");\n        var passwordInput = document.getElementById(\"password\");\n        var loginButton = document.getElementById(\"login-button\");\n\n        // ์ž…๋ ฅ ํ•„๋“œ์˜ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์ œ์ถœ ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์ƒํƒœ ๋ณ€๊ฒฝ\n        useridInput.addEventListener(\"input\", toggleSubmitButton);\n        passwordInput.addEventListener(\"input\", toggleSubmitButton);\n\n        function toggleSubmitButton() {\n            // ์‚ฌ์šฉ์ž๋ช…๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ชจ๋‘ ์ฑ„์›Œ์ ธ ์žˆ์„ ๋•Œ ์ œ์ถœ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”\n            if (useridInput.value.trim() !== \"\" && passwordInput.value.trim() !== \"\") {\n                loginButton.disabled = false;\n            } else {\n                loginButton.disabled = true;\n            }\n        }\n\n\n    });\n</script>\n</body>\n</html>\n",
        "output": "str",
        "x": 860,
        "y": 540,
        "wires": [
            [
                "98add455cdc50a57"
            ]
        ]
    },
    {
        "id": "a05c6ac2e72411e4",
        "type": "http in",
        "z": "b1537c574e27a2f9",
        "name": "HTTP Input",
        "url": "/login_action",
        "method": "post",
        "upload": false,
        "swaggerDoc": "",
        "x": 190,
        "y": 760,
        "wires": [
            [
                "5671a69f69d959b8"
            ]
        ]
    },
    {
        "id": "3ef1ab865351c4d2",
        "type": "http response",
        "z": "b1537c574e27a2f9",
        "name": "HTTP Response",
        "statusCode": "",
        "headers": {},
        "x": 590,
        "y": 760,
        "wires": []
    },
    {
        "id": "5671a69f69d959b8",
        "type": "function",
        "z": "b1537c574e27a2f9",
        "name": "Check Cookie",
        "func": "var loginCookie = msg.req.cookies.COOKIE; // ์ฟ ํ‚ค ์ด๋ฆ„์ด \"COOKIE\"์ธ ๊ฒฝ์šฐ\nif (loginCookie) {\n    // ์ฟ ํ‚ค๋ฅผ ํ™•์ธํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ถ”๊ฐ€์ ์ธ ๋กœ์ง ๊ตฌํ˜„\n    // ์—ฌ๊ธฐ์—๋Š” ์ฟ ํ‚ค๋ฅผ ํ•ด์„ํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๋Š” ๋กœ์ง ๋“ฑ์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n    // ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ๋ผ๋ฉด ๋Œ€์‹œ๋ณด๋“œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ\n    return { payload: null, redirect: \"http://192.168.0.18/ui\" };\n} else {\n    // ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์คŒ\n    return { payload: \"you have to login\" };\n}\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 760,
        "wires": [
            [
                "3ef1ab865351c4d2"
            ]
        ]
    },
    {
        "id": "98add455cdc50a57",
        "type": "http response",
        "z": "b1537c574e27a2f9",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 1070,
        "y": 540,
        "wires": []
    },
    {
        "id": "c913d0c7d0a84945",
        "type": "debug",
        "z": "b1537c574e27a2f9",
        "name": "debug 2",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1240,
        "y": 560,
        "wires": []
    },
    {
        "id": "23139c6750c1e4b8",
        "type": "MySQLdatabase",
        "name": "",
        "host": "127.0.0.1",
        "port": "3306",
        "db": "project",
        "tz": "",
        "charset": "UTF8"
    }
]

Still trying to work through the logic. However:

  • You are not using HTTPS which means that all passwords are already potentially compromised. HTTPS must be implemented before any login.
  • action="http://192.168.0.18:1880/login_action" is very fragile. I think that action="./login_action" would be more reliable?

The function named "์ž…๋ ฅํ•œ value์— ๋Œ€ํ•ด set global" - when setting, you've only allowed for a SINGLE USER. If multiple people try to log in, only the last person to do so would be recorded - is that what you wanted?

And, you haven't validated the inputs. While you've tried to validate user input in the browser, this is not sufficient and isn't secure. You MUST validate on receipt. Make sure the required inputs are not blank, do not exceed whatever sizes you've specified in your database and that they contain valid text that your DB can handle.

The function named "์ž…๋ ฅํ•œ Username์ด mySQL์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ" seems to contain the most fundamental and terrible mistake any developer can make!

You appear to be appending raw user input to a SQL query?


You MUST use a prepared statement for your query (that is more efficient anyway) and you MUST ensure that the above is not possible.

2 Likes

Thank you for your advice.
That's a very bad coding for security.
How do I query SQL to be safe..?

Then, using the prepared statement like [ PREPARE insert_user FROM 'INSERT INTO users (username, email) VALUES (?, ?)'; ] instead of the [SELECT ~~ ], right?
I'll give it a try.
Thank you for your help!!

1 Like

Yes, but make sure you trim and sanitise the input strings. For example, your user names probably no longer than 64 chars and your passwords probably the same. Passwords also not less than 8 chars. Email address also no longer than 64 chars perhaps and must contain an @. And none of the inputs should contain ", ; or '. Also inputs should only contain valid visible characters.

Passwords also should never be stored as text. They should always be hashed, your server never needs to know the actual password, only the hash. That way, the password string cannot be reconstituted from the stored hashes.

1 Like

So, all I have to do is

  1. The id/pw-related input value stored in the global variable should be deleted.
  2. It is necessary to verify the input value.
  3. You have to encrypt and save your password.

I have a question!
How do I verify the input value of a password or ID in Nodered?
And the sqlquery that I used before
[ msg.topic = "SELECT * FROM users WHERE UserID = ? OR Email = ?"; ]
Is it also a security risk to write it like this?

I have one more question....
I want to display the successful login username on the dashboard. And I also want to have my user ID and logout time logged in db when I log out, do I have to reset the global variable? Is there any other way?

The point of a hash is that it is a 1-way cryptographic formula. As long as you apply the same formula to the same input, you get the same hash. So it is the hashes that you compare. The hash is stored in the db instead of the password text. When a user logs in, you query the db for the user details (based on their id), then re-hash their inputted password and compare against the hash from the db. Node.js has a native crypto library you can use or there may well be a suitable contributed node. The important thing being that you hash the password as early as possible and destroy the clear-text password to prevent sniffing.

Yes, it is still susceptable to injection attacks. Always use a prepared statement (which is more efficient anyway), and add quotes to your input strings (having sanitised the input strings as well of course.

If I remember rightly (been a while since I used SQL, this would be better - but only as a prepared statement and with sanitised inputs:

SELECT LIMIT 1 * FROM users WHERE UserID = "?" OR Email = "?"

Note the limit 1 as well as the quotes.

There is one more area of obstruction.
Didn't you say that the password should be hashed and stored in the database? But the bcrypt module is not available in Nordred.
So, I found it in Palette and tried this module. (
However, the password that was encrypted and stored at the time of membership registration and the value that encrypted the input value at the time of login do not match each other!
I expect it to be a problem caused by different salt values, but I can't think of a solution..

Closing this thread since you opened a new thread about this obstruction