- 需要支付一次性的5美元
- 需要使用非国内卡
参考:
记录一下使用typescript开发chrome扩展的相关配置。
必定需要用到的开发依赖项:
npm install chrome-types webpack-cli ts-loader typescript copy-webpack-plugin --save-dev
npx tsc --init
const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
mode: 'production',
entry: {
index: {
import: './src/index.ts',
filename: 'index.js'
},
background: {
import: './src/background.ts',
filename: 'background.js'
}
},
plugins: [
new CopyPlugin({
patterns: [
{ from: "public", to: "" },
],
}),
],
module: {
rules: [
{
test: /.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
clean: true
},
};
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "webpack build"
}
国内提供Oauth认证服务的好少,使用第三方登录降低了认证服务的复杂度,也降低了用户的决策难度,这里用Gitee作为一个示例。应该适用于大多数的Web应用,只有客户端获取authorization_code部分的代码可能不一致。Parse官方文档里的自定义认证写的语焉不详,折腾了好久。
主要需要实现两个函数:validateAuthData和validateAppId。
const qs = require('querystring')
const Parse = require('parse/node')
async function getAuthData(code, redirect_uri, options) {
const token = await codeToToken(code, redirect_uri, options)
const user = await userInfo(token.token_type, token.access_token)
return {
id: user.id,
access_token: token.access_token
}
}
async function codeToToken(code, redirect_uri, options) {
const url = `https://gitee.com/oauth/token`
const query = qs.stringify({
grant_type: 'authorization_code',
code,
redirect_uri,
client_id: options.client_id,
client_secret: options.client_secret
})
const res = await fetch(`${url}?${query}`, { method: 'POST' })
return await res.json()
}
async function userInfo(type, token) {
const url = 'https://gitee.com/api/v5/user'
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `${type} ${token}`
}
})
return await res.json()
}
// Returns a promise that fulfills if this user id is valid.
async function validateAuthData(authData, options) {
if (!authData.id) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少id字段');
}
if (!authData.access_token) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少access_token字段');
}
const user = await userInfo('bearer', authData.access_token)
if (user && String(user.id) === String(authData.id)) {
return
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'authData is invalid for this user.');
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId(appIds, authData, options) {
console.log('validateAppId:', appIds, authData, options)
return Promise.resolve();
}
module.exports = {
validateAuthData,
validateAppId,
getAuthData
}
const gitee = require('../auth/gitee')
const config = require('./config')
Parse.Cloud.define("giteeAuthData", async (request) => {
return await gitee.getAuthData(request.params.code, request.params.redirect_uri, config.auth.gitee)
})
async function login() {
const redirectUrl = chrome.identity.getRedirectURL()
logger.log('redirect_url:', redirectUrl)
// 获取认证码
const authUrl = await _oauthCode(redirectUrl)
if (authUrl === undefined) {
throw new Error('login failed')
}
const url = new URL(authUrl)
const code = url.searchParams.get('code')
if (code == null) {
throw new Error('login failed')
}
logger.log('code:', code)
// 获取认证信息
const authData = await _oauthData(code, redirectUrl)
user.value = await Parse.User.logInWith('gitee', {
authData
})
}
async function _oauthData(code: string, redirect_uri: string) {
const authData = await Parse.Cloud.run('giteeAuthData', {
code,
redirect_uri
})
logger.log('authData:', authData)
return authData
}
[1] Parse 自定义认证文档地址, Parse Server Guide | Parse (parseplatform.org)
[2] Gitee Oauth文档,Gitee OAuth 文档
本来想使用oauth来实现登录,但是国内提供oauth服务要么比较小众,要么居然收费的?传统的用户注册登录使用起来过于繁琐了,很容易把用户挡在最开始的地方,最后决定添加邮箱验证码登录。
Parse Platform的文档中提到了一个Parse.User.become()
方法,但是需要传递一个session token进去,翻了很多遍文档也没发现该如何获取这个session_token,最后在Github仓库的issue中搜到2023年Parse增加了一个loginAs
方法,可以通过传递一个userId来将用户登陆进系统,那么通过云函数和Parse.User.become()
就可以实现邮箱验证码登录了。
const VerifyCode = Parse.Object.extend('VerifyCode')
/**
* 发送邮件验证码
*/
Parse.Cloud.define('verifyCode', async (request) => {
const email = request.params.email
const code = generateRandomString(6)
// 记录验证码
const verifyCode = new VerifyCode()
verifyCode.set('email', email)
verifyCode.set('code', code)
verifyCode.set('expiredAt', new Date(Date.now() + 1000 * 60 * 5))
await verifyCode.save(null, { useMasterKey: true })
// 删除过期的验证码记录
const query = new Parse.Query(VerifyCode)
query.lessThan('expiredAt', new Date())
await Parse.Object.destroyAll(await query.find({ useMasterKey: true }), { useMasterKey: true })
// 发送邮件
await Parse.Cloud.sendEmail({
templateName: "verifyCode",
placeholders: { code },
recipient: email
});
}, {
fields: {
email: {
required: true,
type: String
}
},
requireUser: false
})
const VerifyCode = Parse.Object.extend('VerifyCode')
Parse.Cloud.define('login', async (request) => {
if (!request.params.email) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少email字段');
}
if (!request.params.code) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '缺少code字段');
}
const query = new Parse.Query(Parse.User)
query.equalTo('email', request.params.email)
let user = await query.first({ useMasterKey: true })
if (user === undefined) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '用户不存在');
}
// 验证
const codeQuery = new Parse.Query(VerifyCode)
codeQuery.equalTo('email', request.params.email)
codeQuery.equalTo('code', request.params.code)
codeQuery.greaterThan('expiredAt', new Date())
const code = await codeQuery.first({ useMasterKey: true })
if (code === undefined) {
throw new Parse.Error(Parse.Error.OTHER_CAUSE, '验证码已过期');
}
// 修改邮箱验证字段为验证通过状态
user.set('emailVerified', true)
user.save(null, { useMasterKey: true })
// 登录用户
user = await Parse.User.loginAs(user.id)
return user.getSessionToken()
}, {
fields: {
email: {
required: true,
type: String
},
code: {
required: true,
type: String
}
}
})
export async function emailLogin(email: string, code: string) {
const token = await Parse.Cloud.run('login', { email, code })
return await Parse.User.become(token)
}
[1] 设置当前用户,JavaScript 开发人员指南 |解析 (parseplatform.org)
[3] Parse Server API Mail Adapter,parse-server-api-mail-adapter – npm (npmjs.com)