diff --git a/models/communities.ts b/models/communities.ts new file mode 100644 index 00000000..709a3073 --- /dev/null +++ b/models/communities.ts @@ -0,0 +1,19 @@ +import mongoose from "mongoose" + +export const CommunitiesSchama = new mongoose.Schema({ + uuid: { + type: String, + required: true, + unique: true, + }, + name: { + type: String, + required: true, + }, + description: { + type: String, + }, + timestamp: { type: Date, default: Date.now }, +}) +const csrfToken = mongoose.model("communities", CommunitiesSchama) +export default csrfToken diff --git a/models/tempUsers.ts b/models/tempUsers.ts index 63eb08aa..0734111b 100644 --- a/models/tempUsers.ts +++ b/models/tempUsers.ts @@ -12,16 +12,9 @@ export const tempUsersSchema = new mongoose.Schema({ message: (props: { value: any }) => `${props.value} is not a valid mail address!`, }, }, - key: { - type: String, - required: true, - unique: true, - }, checkCode: { type: Number, required: true, - min: 0, - max: 4294967295, }, checked: { type: Boolean, @@ -31,7 +24,11 @@ export const tempUsersSchema = new mongoose.Schema({ type: Number, default: 0, }, - timestamp: { type: Date, default: Date.now }, + token: { + type: String, + required: true, + }, + timestamp: { type: Date, default: Date.now, expires: 60 * 60 * 24 }, }) const tempUsers = mongoose.model("tempUsers", tempUsersSchema) export default tempUsers diff --git a/routes/api/v2/client/block/user.ts b/routes/api/v2/client/block/user.ts index eafa5fc5..524edb1b 100644 --- a/routes/api/v2/client/block/user.ts +++ b/routes/api/v2/client/block/user.ts @@ -6,6 +6,7 @@ import takos from "../../../../../util/takos.ts" import { load } from "$std/dotenv/mod.ts" import userConfig from "../../../../../models/userConfig.ts" import users from "../../../../../models/users.ts" +import friends from "../../../../../models/friends.ts" const env = await load() export const handler = { async POST(req: Request, ctx: any) { @@ -55,7 +56,7 @@ export const handler = { }, body: JSON.stringify({ userid: ctx.state.data.userid, blockedUser: userid, signature: new Uint8Array(signature) }), }) - if(remoteServer.status !== 200) { + if (remoteServer.status !== 200) { return new Response(JSON.stringify({ status: false, message: "Failed to block user on remote server" }), { headers: { "Content-Type": "application/json" }, status: 200, @@ -63,6 +64,10 @@ export const handler = { } } //useridとfriendidが入っているfriendroomを削除 + const roomtype = userDomain == env["DOMAIN"] ? "friend" : "remotefriend" + //友達リストから削除 + await friends.updateOne({ userID: ctx.state.data.userid }, { $pull: { friends: { $in: [userid] } } }) + await userConfig.updateOne({ userID: ctx.state.data.userid, types: roomtype }, { $pull: { friendRooms: { $in: [userid] } } }) return new Response(JSON.stringify({ status: true }), { headers: { "Content-Type": "application/json" }, }) diff --git a/routes/api/v2/client/create/community.ts b/routes/api/v2/client/create/community.ts index 661ebd29..9098635e 100644 --- a/routes/api/v2/client/create/community.ts +++ b/routes/api/v2/client/create/community.ts @@ -2,3 +2,15 @@ // POST /api/v2/client/create/community // { name: string, description: string, csrftoken: string, icon: file } // -> { status: boolean, message: string } +export const handler = { + async POST(req: Request, ctx: any) { + if (!ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Please Login" }), { + headers: { "Content-Type": "application/json" }, + status: 401, + }) + } + const body = await req.json() + const { name, description, csrftoken, icon } = body + }, +} diff --git a/routes/api/v2/client/sessions/login.ts b/routes/api/v2/client/sessions/login.ts index 2ca16a12..fc40ffaa 100644 --- a/routes/api/v2/client/sessions/login.ts +++ b/routes/api/v2/client/sessions/login.ts @@ -2,3 +2,97 @@ // POST /api/v2/client/sessions/login // { email?: string, userName?: string, password: string} // -> { status: boolean, message: string } cookie: sessionid=string; path=/; max-age=number; httpOnly; SameSite=Strict; +import users from "../../../../../models/users.ts" +import sessionID from "../../../../../models/sessionid.ts" +export const handler = { + async POST(req: Request, ctx: any) { + if (ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Already Logged In" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const body = await req.json() + const { email, userName, password } = body + if (typeof password !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid password" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof email !== "string" && typeof userName !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid email or userName" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + //emailでログイン + let user + if (typeof email === "string") { + user = await users.findOne({ email: email }) + if (user === null) { + return new Response(JSON.stringify({ status: false, message: "User not found" }), { + headers: { "Content-Type": "application/json" }, + status: 404, + }) + } + } + //userNameでログイン + if (typeof userName === "string") { + user = await users.findOne({ userName: userName }) + if (user === null) { + return new Response(JSON.stringify({ status: false, message: "User not found" }), { + headers: { "Content-Type": "application/json" }, + status: 404, + }) + } + } + if (user === null || user === undefined) { + return new Response(JSON.stringify({ status: false, message: "User not found" }), { + headers: { "Content-Type": "application/json" }, + status: 404, + }) + } + const salt = user.salt + const hash = user.password + const saltPassword = password + salt + const reqHash = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(saltPassword), + ) + const hashArray = new Uint8Array(reqHash) + const hashHex = Array.from( + hashArray, + (byte) => byte.toString(16).padStart(2, "0"), + ).join("") + if (hash !== hashHex) { + return new Response( + JSON.stringify({ "status": false, error: "password" }), + { + headers: { "Content-Type": "application/json" }, + status: 403, + }, + ) + } + const sessionIDarray = new Uint8Array(64) + const randomarray = crypto.getRandomValues(sessionIDarray) + const sessionid = Array.from( + randomarray, + (byte) => byte.toString(16).padStart(2, "0"), + ).join("") + const result = await sessionID.create({ + userid: user.uuid, + sessionID: sessionid, + }) + + if (result !== null) { + return new Response(JSON.stringify({ "status": true }), { + headers: { + "Content-Type": "application/json", + "Set-Cookie": `sessionid=${sessionid}; Path=/; Max-Age=2592000;`, + }, + status: 200, + }) + } + }, +} diff --git a/routes/api/v2/client/sessions/logout.ts b/routes/api/v2/client/sessions/logout.ts index c8cefc01..263dc726 100644 --- a/routes/api/v2/client/sessions/logout.ts +++ b/routes/api/v2/client/sessions/logout.ts @@ -1,3 +1,32 @@ //sessionidを削除してcookieを削除する // POST /api/v2/client/sessions/logout +// { csrftoken: string } // -> { status: boolean, message: string } +import sessionID from "../../../../../models/sessionid.ts" +import takos from "../../../../../util/takos.ts" +export const handler = { + async POST(req: Request, ctx: any) { + if (!ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Already Logged Out" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const body = await req.json() + if (await takos.checkCsrfToken(body.csrftoken) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid CSRF token" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + await sessionID.deleteOne({ sessionID: ctx.state.data.sessionID }) + //cookieを削除するheaderを返す + return new Response(JSON.stringify({ status: true, message: "Logged Out" }), { + headers: { + "Content-Type": "application/json", + "Set-Cookie": `sessionid=; path=/; max-age=0; httpOnly; SameSite=Strict;`, + }, + status: 200, + }) + }, +} diff --git a/routes/api/v2/client/sessions/registers/auth.ts b/routes/api/v2/client/sessions/registers/auth.ts index ae2ea06d..88bddad1 100644 --- a/routes/api/v2/client/sessions/registers/auth.ts +++ b/routes/api/v2/client/sessions/registers/auth.ts @@ -1,4 +1,142 @@ //本登録するapi //POST /api/v2/client/sessions/registers/auth -// { email: string, password: string, nickName: string,age: string, token: string, recaptcha: string } +// { email: string, password: string, nickName: string,age: string, token: string, recaptcha: string, userName: string} // -> { status: boolean, message: string } +import { load } from "$std/dotenv/mod.ts" +import tempUsers from "../../../../../../models/tempUsers.ts" +import users from "../../../../../../models/users.ts" +import takos from "../../../../../../util/takos.ts" +const env = await load() +export const handler = { + async POST(req: Request, ctx: any) { + if (ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Already Logged In" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const body = await req.json() + const { email, password, nickName, age, token, recaptcha, userName } = body + if (takos.checkEmail(email) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid email" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (takos.checkUserName(userName) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid userName" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (takos.checkNickName(nickName) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid nickName" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (takos.checkAge(age) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid age" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (takos.checkPassword(password) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid password" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof token !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid token" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const isSecsusRechapcha = await fetch( + `https://www.google.com/recaptcha/api/siteverify?secret=${Deno.env.get("RECAPTCHA_SECRET_KEY")}&response=${recaptcha}`, + ) + const score = await isSecsusRechapcha.json() + if (score.score < 0.5 || score.success == false) { + console.log(score) + return new Response( + JSON.stringify({ "status": false, error: "rechapcha" }), + { + headers: { "Content-Type": "application/json" }, + status: 403, + }, + ) + } + const tempUser = await tempUsers.findOne({ + email: email, + token: token, + }) + if (tempUser === null) { + return new Response(JSON.stringify({ status: false, message: "Invalid token" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (tempUser.checked) { + return new Response(JSON.stringify({ status: false, message: "Already Registered" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const user = await users.findOne({ + email: email, + }) + if (user !== null) { + return new Response(JSON.stringify({ status: false, message: "Already Registered" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + //ユーザー名がかぶっていないか確認 + const userNameUser = await users.findOne({ + userName: userName, + }) + if (userNameUser !== null) { + return new Response(JSON.stringify({ status: false, message: "Already Registered" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + //本登録 + await tempUsers.deleteOne({ + email: email, + }) + //塩を生成 + const array = new Uint8Array(32) + crypto.getRandomValues(array) + const salt = Array.from( + array, + (byte) => byte.toString(16).padStart(2, "0"), + ).join("") + //パスワードをハッシュ化 + const saltPassword = password + salt + const hash = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(saltPassword), + ) + const hashArray = new Uint8Array(hash) + const hashHex = Array.from( + hashArray, + (byte) => byte.toString(16).padStart(2, "0"), + ).join("") + //ユーザーを登録 + const uuid = crypto.randomUUID() + "@" + env["serverDomain"] + await users.create({ + uuid: uuid, + userName, + nickName, + mail: email, + password: hashHex, + salt: salt, + age: age, + }) + return new Response(JSON.stringify({ status: true }), { + headers: { "Content-Type": "application/json" }, + }) + }, +} diff --git a/routes/api/v2/client/sessions/registers/check.ts b/routes/api/v2/client/sessions/registers/check.ts index a94a84fc..11f9523c 100644 --- a/routes/api/v2/client/sessions/registers/check.ts +++ b/routes/api/v2/client/sessions/registers/check.ts @@ -1,3 +1,78 @@ //メールアドレスに送られた確認コードを認証する // POST /api/v2/client/sessions/registers/check // { email: string, code: string, token: string, recaptcha: string } +// -> { status: boolean, message: string } +import { load } from "$std/dotenv/mod.ts" +import * as mod from "$std/crypto/mod.ts" +import tempUsers from "../../../../../../models/tempUsers.ts" +const env = await load() +const secretKey = env["rechapcha_seecret_key"] +export const handler = { + async POST(req: Request, ctx: any) { + if (ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Already Logged In" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const body = await req.json() + const email = body.email + const code = body.code + const token = body.token + const recaptcha = body.recaptcha + if (typeof email !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid email" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof code !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid code" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof token !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid token" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof recaptcha !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid recaptcha" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const isSecsusRechapcha = await fetch( + `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptcha}`, + ) + const score = await isSecsusRechapcha.json() + if (score.score < 0.5 || score.success == false) { + console.log(score) + return new Response( + JSON.stringify({ "status": false, error: "rechapcha" }), + { + headers: { "Content-Type": "application/json" }, + status: 403, + }, + ) + } + const tempUser = await tempUsers.findOne({ + email: email, + token: token, + checkCode: code, + }) + if (tempUser === null) { + return new Response(JSON.stringify({ status: false, message: "Invalid code" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + await tempUsers.updateOne({ token: token }, { $set: { checked: true } }) + return new Response(JSON.stringify({ status: true }), { + headers: { "Content-Type": "application/json" }, + status: 200, + }) + }, +} diff --git a/routes/api/v2/client/sessions/registers/temp.ts b/routes/api/v2/client/sessions/registers/temp.ts index 4dec9455..2fab2321 100644 --- a/routes/api/v2/client/sessions/registers/temp.ts +++ b/routes/api/v2/client/sessions/registers/temp.ts @@ -2,3 +2,94 @@ // POST /api/v2/client/sessions/registers/temp // { email: string, recaptcha: string } // -> { status: boolean, message: string, token: string } +import takos from "../../../../../../util/takos.ts" +import tempUsers from "../../../../../../models/tempUsers.ts" +import users from "../../../../../../models/users.ts" +import { sendMail } from "../../../../../../util/takoFunction.ts" +import { load } from "$std/dotenv/mod.ts" +import * as mod from "$std/crypto/mod.ts" +const env = await load() +const secretKey = env["rechapcha_seecret_key"] +const handler = { + async POST(req: Request, ctx: any) { + if (ctx.state.data.loggedIn) { + return new Response(JSON.stringify({ status: "Already Logged In" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const body = await req.json() + const email = body.email + const recaptcha = body.recaptcha + if (typeof email !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid email" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (takos.checkEmail(email) === false) { + return new Response(JSON.stringify({ status: false, message: "Invalid email" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + if (typeof recaptcha !== "string") { + return new Response(JSON.stringify({ status: false, message: "Invalid recaptcha" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + const isSecsusRechapcha = await fetch( + `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptcha}`, + ) + const score = await isSecsusRechapcha.json() + if (score.score < 0.5 || score.success == false) { + console.log(score) + return new Response( + JSON.stringify({ "status": false, error: "rechapcha" }), + { + headers: { "Content-Type": "application/json" }, + status: 403, + }, + ) + } + const randomNumber = takos.generateRandom16DigitNumber() + //すでに登録されているユーザーかどうかを確認 + const user = await users.findOne({ + email: email, + }) + if (user !== null) { + return new Response(JSON.stringify({ status: false, message: "Already Registered" }), { + headers: { "Content-Type": "application/json" }, + status: 400, + }) + } + //データーベースに仮登録情報を保存 すでに登録されている場合は更新 + const sessionid = takos.createSessionid() + const tempUser = await tempUsers.findOne({ + email: email, + }) + if (tempUser === null) { + await tempUsers.create({ + email: email, + checkCode: randomNumber, + token: sessionid, + }) + } else { + await tempUsers.updateOne({ + email: email, + }, { + $set: { + checkCode: randomNumber, + token: sessionid, + }, + }) + } + //仮登録のメールを送信 + sendMail(email, "認証コード", `以下のtokenを張り付けてメールアドレスを認証してください.\ntoken: ${randomNumber}`) + return new Response(JSON.stringify({ status: true, message: "Sent Mail", token: sessionid }), { + headers: { "Content-Type": "application/json" }, + status: 200, + }) + }, +} diff --git a/routes/api/v2/client/settings/privacy.ts b/routes/api/v2/client/settings/privacy.ts index 51d6d90c..4aacce3e 100644 --- a/routes/api/v2/client/settings/privacy.ts +++ b/routes/api/v2/client/settings/privacy.ts @@ -1,4 +1,4 @@ //設定を変更するapi //POST /api/v2/client/settings/privacy // { setting: { ... }, csrftoken: string } -// -> { status: boolean, message: string } \ No newline at end of file +// -> { status: boolean, message: string } diff --git a/util/takos.ts b/util/takos.ts index 5a903f43..54b49043 100644 --- a/util/takos.ts +++ b/util/takos.ts @@ -5,14 +5,14 @@ let env = await load() const generateKeyPair = async () => { const keyPair = await window.crypto.subtle.generateKey( { - name: "RSASSA-PKCS1-v1_5", - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), - hash: { name: "SHA-256" }, + name: "RSASSA-PKCS1-v1_5", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: { name: "SHA-256" }, }, true, - ["sign", "verify"] - ); + ["sign", "verify"], + ) return keyPair } const takos = { @@ -33,29 +33,29 @@ const takos = { domain: split[1], } }, - signData: async (data: string, privateKey: CryptoKey): Promise =>{ + signData: async (data: string, privateKey: CryptoKey): Promise => { const signAlgorithm = { - name: "RSASSA-PKCS1-v1_5", - hash: { name: "SHA-256" } - }; + name: "RSASSA-PKCS1-v1_5", + hash: { name: "SHA-256" }, + } const signature = await window.crypto.subtle.sign( - signAlgorithm, - privateKey, - new TextEncoder().encode(data) - ); + signAlgorithm, + privateKey, + new TextEncoder().encode(data), + ) return signature }, verifySignature: async (publicKey: CryptoKey, signature: ArrayBuffer, data: string): Promise => { const signAlgorithm = { - name: "RSASSA-PKCS1-v1_5", - hash: { name: "SHA-256" } - }; + name: "RSASSA-PKCS1-v1_5", + hash: { name: "SHA-256" }, + } return await window.crypto.subtle.verify( - signAlgorithm, - publicKey, - signature, - new TextEncoder().encode(data) - ); + signAlgorithm, + publicKey, + signature, + new TextEncoder().encode(data), + ) }, getPrivateKey: async () => { const server = await serverInfo.findOne({ serverDomain: env["DOMAIN"] }) @@ -70,14 +70,14 @@ const takos = { }) return keyPair.privateKey } - if(server.privatekey === null){ + if (server.privatekey === null) { const keyPair = await generateKeyPair() const privateKey = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey) const publicKey = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey) await serverInfo.updateOne({ serverDomain: env["DOMAIN"] }, { privatekey: JSON.stringify(privateKey), publickey: JSON.stringify(publicKey) }) return keyPair.privateKey } - if(server.lastupdatekey < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 7)){ + if (server.lastupdatekey < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 7)) { const keyPair = await generateKeyPair() const privateKey = await window.crypto.subtle.exportKey("jwk", keyPair.privateKey) const publicKey = await window.crypto.subtle.exportKey("jwk", keyPair.publicKey) @@ -93,23 +93,66 @@ const takos = { await serverInfo.updateOne({ serverDomain: env["DOMAIN"] }, { privatekey: JSON.stringify(privateKey), publickey: JSON.stringify(publicKey) }) return keyPair.privateKey } - } + }, + checkEmail: (email: string) => { + const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + return emailPattern.test(email) + }, + generateRandom16DigitNumber(): string { + const array = new Uint8Array(8) // 8バイト(64ビット)を生成 + crypto.getRandomValues(array) + let randomNumber = "" + for (const byte of array) { + randomNumber += byte.toString().padStart(2, "0") + } + // 16桁にトリム + const StringResult = randomNumber.slice(0, 16) + return StringResult + }, + createSessionid: () => { + const sessionIDarray = new Uint8Array(64) + const randomarray = crypto.getRandomValues(sessionIDarray) + const sessionid = Array.from( + randomarray, + (byte) => byte.toString(32).padStart(2, "0"), + ).join("") + return sessionid + }, + checkUserName: (userName: string) => { + const userNamePattern = /^[a-zA-Z0-9]{4,16}$/ + return userNamePattern.test(userName) + }, + checkNickName: (nickName: string) => { + // 1文字以上、16文字以下ひらがな、カタカナ、漢字、半角英数字、 + const nickNamePattern = /^[ぁ-んァ-ヶ一-龠a-zA-Z0-9]{1,16}$/ + return nickNamePattern.test(nickName) + }, + checkPassword: (password: string) => { + const passwordPattern = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,100}$/ + return passwordPattern.test(password) + }, + checkAge: (age: number) => { + if (age < 0 || age > 200) { + return false + } + return true + }, } export default takos async function importCryptoKey(keyData: string | undefined): Promise { if (keyData === undefined) { - throw new Error("keyData is undefined"); + throw new Error("keyData is undefined") } - const jwkKey = JSON.parse(keyData); + const jwkKey = JSON.parse(keyData) const cryptoKey = await window.crypto.subtle.importKey( "jwk", // インポート形式 jwkKey, // インポートするキーデータ - { // キーの使用目的とアルゴリズム + { // キーの使用目的とアルゴリズム name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" }, }, true, // エクスポート可能かどうか - ["sign"] // 秘密鍵の場合は["sign"]、公開鍵の場合は["verify"] - ); - return cryptoKey; -} \ No newline at end of file + ["sign"], // 秘密鍵の場合は["sign"]、公開鍵の場合は["verify"] + ) + return cryptoKey +}