Node cookie-session的驗證機制

什麼是HTTP & HTTP如何運作

HTTP遵循客戶端(client)-伺服器端(server)的模式
client =>(發送請求,request) => server(根據請求內容) =>(發送回應,response)=>client

圖片來源https://bytesofgigabytes.com/networking/how-http-request-and-response-works/

圖片來源:MDN HTTP Messages

安裝cookie-session

將客戶端的會話資料儲存在 cookie 中
透過npm安裝
npm cookie-session

1
2
nvm use 16.14.0
npm install cookie-session --save

在Node express使用

1
2
3
4
5
6
7
8
9
10
11
12
var cookieSession = require('cookie-session')
var express = require('express')

var app = express()

app.use(cookieSession({
name: 'session',
keys: [/* secret keys */],

// Cookie Options
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}))

cookieSession(options)

使用提供的選項建立新的 cookie 會話中間件。 該中間件會將屬性 session 附加到 req,它提供了一個表示已載入會話的物件。 如果請求中未提供有效會話,則該會話是新會話,或是從請求載入的會話。

如果 req.session 的內容被更改,中間件將自動向回應添加 Set-Cookie 標頭。 請注意,除非會話中有內容,否則響應中不會出現 Set-Cookie 標頭(因此不會為特定用戶創建會​​話),因此一旦您有要存儲的標識信息,請務必向 req.session 添加一些內容會議。

Options

Cookie 會話接受選項物件中的這些屬性。
name:要設定的 cookie 的名稱,預設為 session。
keys用於簽署和驗證 cookie 值的金鑰列表,或配置的 Keygrip 實例。 設定 cookie 始終使用 key[0] 進行簽名,而其他金鑰對於驗證有效,從而允許金鑰輪換。 如果提供了 Keygrip 實例,則可以使用它來變更簽章參數,例如簽章演算法。
secret如果未提供鍵,則將用作單一鍵的字串。
Cookie Options
其他選項傳遞給 cookies.get() 和 cookies.set() ,可讓您控制安全性、網域、路徑和簽名等設定。

這些選項還可以包含以下任何內容(有關完整列表,請參閱 cookies 模組文件:

  • maxAge: a number representing the milliseconds from Date.now() for expiry
    最大年齡::代表 Date.now() 到期時間的毫秒數的數字
  • expires: a Date object indicating the cookie's expiration date (expires at the end of session by default).
    過期時間:指示 cookie 過期日期的 Date 物件(預設在會話結束時過期)。
  • path: a string indicating the path of the cookie (/ by default).
    指示 cookie 路徑的字串(預設為 /)。
  • domain: a string indicating the domain of the cookie (no default).
    指示 cookie domain的字串(無預設值)。
  • sameSite: a boolean or string indicating whether the cookie is a "same site" cookie (false by default). This can be set to 'strict', 'lax', 'none', or true (which maps to 'strict').
    一個布林值或字串,指示 cookie 是否是「同一網站」cookie(預設為 false)。 可以將其設定為“strict”、“lax”、“none”或 true(映射到“strict”)。
  • secure: a boolean indicating whether the cookie is only to be sent over HTTPS (false by default for HTTP, true by default for HTTPS). If this is set to true and Node.js is not directly over a TLS connection, be sure to read how to setup Express behind proxies or the cookie may not ever set correctly.
    安全的:一個布林值,指示 cookie 是否僅透過 HTTPS 發送(HTTP 預設為 false,HTTPS 預設為 true)。 如果將此設為 true 並且 Node.js 不是直接透過 TLS 連接,請務必閱讀如何在代理程式後面設定 Express,否則 cookie 可能無法正確設定。
  • httpOnly: a boolean indicating whether the cookie is only to be sent over HTTP(S), and not made available to client JavaScript (true by default).
    僅http:一個布林值,指示 cookie 是否僅透過 HTTP(S) 發送,而不可供客戶端 JavaScript 使用(預設為 true)。
  • signed: a boolean indicating whether the cookie is to be signed (true by default).
    簽署:一個布林值,指示 cookie 是否要簽署(預設為 true)。
  • overwrite: a boolean indicating whether to overwrite previously set cookies of the same name (true by default).
    覆蓋:一個布林值,指示是否覆蓋先前設定的同名 cookie(預設為 true)。

req.session

.isChanged

.isNew

.isPopulated

req.sessionOptions

Destroying a session

Saving a session

參考資料

Node express bcryptjs 加密與驗證

bcryptjs密碼加密

安裝bcryptjs bcryptjs
密碼加密,此套件的加密是不可逆的,所以沒有辦法從加密後的結果回推原始密碼,相對安全性提高非常多

1
npm install bcryptjs --save

異步

1
2
3
4
const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';

技術 1(在單獨的函數呼叫上產生鹽和雜湊值):
bcrypt.hash()

1
2
3
4
5
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
// Store hash in your password DB.
});
});

技術 2(自動產生鹽和雜湊值):
bcrypt.hash()

1
2
3
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});

這兩種技術達到相同的最終結果。

要檢查密碼:

1
2
3
4
5
6
7
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});

bcryptjs npm
nodejs中的bcryptjs密码加密
使用 bcryptjs加密

Vue3 TypeScript 的 Vue-i18n mulitional languages

在Vue3 TypeScript專案內安裝 Vue-i18n

安裝指令

1
npm install vue-i18n@next --save

在 src/ 目錄底下,新增 lang資料夾,並新增zh-TW.json,en-US.json 檔案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//en-US.json
{
"nav_menu":{
"home":"Home",
"login":"Login"
}
}
//zh-TW.json
{
"nav_menu":{
"home":"首頁",
"login":"登入"
}
}

在 src/ 目錄底下,新增 languages/i18n.ts,並設定語系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createI18n } from 'vue-i18n'
import zh from '../lang/zh-TW.json'
import en from '../lang/en-US.json'

type MessageSchema = typeof zh

const i18n = createI18n<[MessageSchema], 'zh-TW' | 'en-US'>({
legacy: false, // 要把 legacy 設為 false,才可以使用 Composition API
locale: 'zh-TW',
fallbackLocale: 'zh-TW',
globalInjection: true,
messages: {
'zh-TW': zh,
'en-US': en
}
})

export default i18n

在 main.ts 引入 Vue-i18n

1
2
3
4
5
6
7
//引入plugins/i18n
import i18n from './plugins/i18n'

const app = createApp(App)
//使用use i18n
app.use(i18n)
app.mount('#app')

在App.vue 載入語系測試使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<div>切換語言:</div>
<select @change="changeLang">
<option value="zh-TW">中文</option>
<option value="en-US">English</option>
</select>
</div>
<p>
{{ $t('nav_menu.home') }}
</p>
</div>
</template>

<script setup lang="ts">
//載入vue-i18n
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
//改變語系切換
const changeLang = (e) => {
locale.value = e.target.value
}
</script>

另一種寫法:pinia

在sores新增nav.ts

  • pinia載入
  • vue 方法 ref, computed 載入
  • vue-i18n載入
  • 數據的Ts撰寫homeMenuType 與載入
1
2
3
4
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useI18n } from "vue-i18n";
import { homeMenuType } from "../types/all";

在types新增all.ts
轉寫
homeMenuType

1
2
3
4
5
export interface homeMenuType{
name?: string |null |any,
id?: string |null |any,
link: string | null | any,
}
  • 使用computed 解析nav_menu.home
  • 放入陣列homeMenu
  • 寫函式 changeLang改變語系
  • return homeMenu,changeLang
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//useNavbarStore 
export const useNavbarStore = defineStore('navbar', () => {
const { locale } = useI18n();
const i18n = useI18n();

const i18nPlatform = computed(() => i18n.t("nav_menu.home"))
const i18nCompany = computed(() => i18n.t("nav_menu.login"))

const homeMenu=ref<homeMenuType[]>([
{ name: i18nPlatform, id: '001', link: '/index' },
{ name: i18nCompany, id: '002', link: '/aboutus' }
])


const changeLang = (e: any) => {
console.log(e)
locale.value = e.target.value
console.log(locale.value)
}
watch(locale, newlocale => {
localStorage.setItem("locale", newlocale);
});

return {
homeMenu, changeLang
}
})

在頁面使用
  • 載入pinia storeToRefs
  • 宣告store = useNavbarStore()
  • 解構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
<div>
<div>
切換語言:
<select @change="changeLang">
<option value="zh-TW">
中文
</option>
<option value="en-US">
English
</option>
</select>
</div>
<ul>
<li v-for="(item, id) in homeMenu"
:key="id">
{{ item.name }}
</li>
</ul>
<router-view />
</div>
</template>

<script setup lang="ts">
//載入pinia storeToRefs
import { storeToRefs } from 'pinia';
import { useNavbarStore } from './stores/nav';
//宣告store = useNavbarStore()
const store = useNavbarStore();
//解構
const { homeMenu } = storeToRefs(store);
const { changeLang } = store;
</script>

<style lang="scss" scoped>
ul{
display: flex;
li {
display : flex;
padding:10px;

}
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

MySql CHECK 檢查限制 (SQL CHECK Constraint)

CHECK 檢查限制 (SQL CHECK Constraint)

CHECK 限制用來約束欄位中的可用值,以保證該欄位中的資料值都會符合您設定的條件。

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE customer (
C_Id INT NOT NULL CHECK (C_Id>0),
Name VARCHAR(50) NOT NULL,
Address VARCHAR(255),
Phone VARCHAR(20)
);
CREATE TABLE customer (
C_Id INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Address VARCHAR(255),
Phone VARCHAR(20),
CHECK (C_Id>0)
);

在 MySQL 增加 CHECK 限制不會有錯誤,但是沒有用,CHECK 並不會被執行喔!

1
2
3
4
5
6
7
CREATE TABLE customer (
C_Id INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Address VARCHAR(255),
Phone VARCHAR(20),
CONSTRAINT chk_Customer CHECK (C_Id>0 AND Name!='XXX')
);

更改資料表限制 ALTER TABLE…

1
ALTER TABLE customer ADD CHECK (C_Id>0);

替主鍵命名與多欄位的組合鍵:

1
2
ALTER TABLE customer
ADD CONSTRAINT chk_Customer CHECK (C_Id>0 AND Name!='XXX');

移除資料表限制 ALTER TABLE…

1
ALTER TABLE customer DROP CONSTRAINT chk_Customer;

MySql DEFAULT 預設值限制 (SQL DEFAULT Constraint)

DEFAULT 預設值限制 (SQL DEFAULT Constraint)

DEFAULT 限制用來設定欄位的預設值。當你在 INSERT 資料時若該欄位沒指定值則會採用預設值。

1
2
3
4
5
6
CREATE TABLE customer (
C_Id INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Address VARCHAR(255) DEFAULT '未知',
Phone VARCHAR(20)
);

更改資料表限制 ALTER TABLE…

1
ALTER TABLE customer ALTER COLUMN Address SET DEFAULT '未知';

SQL Server

1
ALTER TABLE customer ADD DEFAULT '未知' FOR Address;

移除資料表限制 ALTER TABLE…

1
ALTER TABLE customer ALTER COLUMN Address DROP DEFAULT;

SQL Server

1
ALTER TABLE table_name DROP constrain_name;

Oracle

1
ALTER TABLE table_name MODIFY column_name DEFAULT NULL;

Vite element-plus ts 表單驗證

Vite element-plus ts 表單驗證

types資料夾新增 forms.ts

1
2
3
4
5
6
7
export interface ruleFormType {
email: string,
password: string,
checkPass?: string,
verification?: string,
}

在使用的頁面載入

  • 載入ruleFormType
  • 載入ElForm,type定義ElForm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { ref, onMounted,watchEffect } from 'vue'
import { ruleFormType } from '../types/forms.ts'
import type { ElForm } from 'element-plus'
//找不到 ‘element-plus’ 不得使用命名空間 ‘FormInstance’ 作為類型
type FormInstance = InstanceType<typeof ElForm>
const ruleFormRef = ref<FormInstance>()
//定義表單
const ruleForm = ref<ruleFormType>({
email : '',
password : '',
checkPass: '',
verification:''
})
//驗證規則
const rules = ref<FormRules<ruleFormType>>({
email: [
{
required: true,
message : '請輸入電子信箱',
trigger : 'blur',
},
{
type : 'email',
message: '電子信箱格式不符',
trigger: [
'blur',
'change'
],
},
],
password : [{ validator: validatePassword, trigger: 'blur' }],
checkPass: [{ validator: checkPassword, trigger: 'blur' }],
})

驗證的規則與函式
一 密碼為空時,密碼不為空時驗證確認密碼
二 密碼與確認密碼不相同時
三 點擊送出沒有值時出現
四 重置表單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<template>
<div>
<el-form ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm">
<el-form-item prop="email"
label="電子郵件">
<el-input v-model="ruleForm.email" />
</el-form-item>
<el-form-item label="密碼"
prop="password">
<el-input v-model="ruleForm.password"
type="password"
autocomplete="off" />
</el-form-item>
<el-form-item label="確認密碼"
prop="checkPass">
<el-input v-model="ruleForm.checkPass"
type="password"
autocomplete="off" />
</el-form-item>
<!---->
<el-form-item>
<el-button @click="resetForm(ruleFormRef)">
重置
</el-button>
<el-button type="primary"
@click="submitForm(ruleFormRef)">
送出
</el-button>
</el-form-item>
</el-form>
</div>
</template>

<script setup lang="ts">
// @ts-ignore
import { ref, onMounted } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { ElForm } from 'element-plus'
// @ts-ignore
import { ruleFormType } from '../types/forms.ts'

//找不到 ‘element-plus’ 不得使用命名空間 ‘FormInstance’ 作為類型
type FormInstance = InstanceType<typeof ElForm>
const ruleFormRef = ref<FormInstance>()

const ruleForm = ref<ruleFormType>({
email : '',
password : '',
checkPass: '',
verification:''
})
//驗證密碼
const validatePassword = (_rule: any, value: any, callback: any) => {
if (value === '') {
//如果空值時驗證
callback(new Error('請輸入密碼'))
} else {
//如果密碼不等於空
if (ruleForm.value.checkPass !== '') {
if (!ruleFormRef.value) return
ruleForm.value.validateField('checkPass', () => null)
}
callback()
}
}
// 檢查密碼是否兩次相同
const checkPassword= (_rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('請再輸入一次密碼'))

} else if (value !== ruleForm.value.password) {
callback(new Error('兩次密碼不相同!'))
} else {
callback()
}
}
//驗證規則
type FormRules = InstanceType<typeof ElForm>
const rules = ref<FormRules>({
email: [
{
required: true,
message : '請輸入電子信箱',
trigger : 'blur',
},
{
type : 'email',
message: '電子信箱格式不符',
trigger: [
'blur',
'change'
],
},
],
password : [{ validator: validatePassword, trigger: 'blur' }],
checkPass: [{ validator: checkPassword, trigger: 'blur' }],
})

const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
console.log('submit!')
} else {
console.log('驗證', fields)
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
onMounted(() => {

});
</script>

Github 專案說明
element-plus form validation

MySql CROSS JOIN 關鍵字 (SQL CROSS JOIN Keyword) - 交叉連接

CROSS JOIN 關鍵字 (SQL CROSS JOIN Keyword) - 交叉連接

交叉連接為兩個資料表間的笛卡兒乘積 (Cartesian product),兩個資料表在結合時,不指定任何條件,即將兩個資料表中所有的可能排列組合出來,以下例而言 CROSS JOIN 出來的結果資料列數為 3×5=15 筆,因此,當有 WHERE、ON、USING 條件時不建議使用。

1
2
3
4
5
6
7
8
9
10
SELECT table_column1, table_column2...
FROM table_name1
CROSS JOIN table_name2;
//or
SELECT table_column1, table_column2...
FROM table_name1, table_name2;
//or
SELECT table_column1, table_column2...
FROM table_name1
JOIN table_name2;

Example

1
2
3
SELECT customers.Name, orders.Order_No
FROM customers
CROSS JOIN orders;