hash clientSecret and better logging for sdk

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-10-12 13:51:11 +02:00
parent 888d6db16b
commit 5e382911a8
7 changed files with 115 additions and 40 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
packages/sdk/profileId.txt
packages/sdk/test.ts packages/sdk/test.ts
# Logs # Logs

View File

@@ -18,10 +18,29 @@ For pushing events
import { Mixan } from '@mixan/sdk'; import { Mixan } from '@mixan/sdk';
const mixan = new Mixan({ const mixan = new Mixan({
clientSecret: '9fb405d2-7e16-489f-980c-67b25a6eab97', clientId: 'uuid',
url: 'http://localhost:8080', clientSecret: 'uuid',
url: 'http://localhost:8080/api/sdk',
batchInterval: 10000, batchInterval: 10000,
verbose: false verbose: false,
saveProfileId(id) {
// Web
localStorage.setItem('@profileId', id)
// // react-native-mmkv
// mmkv.setItem('@profileId', id)
},
removeProfileId() {
// Web
localStorage.removeItem('@profileId')
// // react-native-mmkv
// mmkv.delete('@profileId')
},
getProfileId() {
// Web
return localStorage.getItem('@profileId')
// // react-native-mmkv
// return mmkv.getString('@profileId')
},
}) })
mixan.setUser({ mixan.setUser({

View File

@@ -14,9 +14,7 @@ app.use(morgan(':method :url :status :response-time ms'))
// Public routes // Public routes
app.get('/', (req, res) => res.json('Welcome to Mixan')) app.get('/', (req, res) => res.json('Welcome to Mixan'))
if (process.env.SETUP) { app.use('/setup', setup)
app.use('/setup', setup)
}
// Protected routes // Protected routes
app.use(authMiddleware) app.use(authMiddleware)

View File

@@ -1,27 +1,45 @@
import { NextFunction, Request, Response } from "express" import { NextFunction, Request, Response } from 'express'
import { db } from "../db" import { db } from '../db'
import { createError } from "../responses/errors" import { HttpError, createError } from '../responses/errors'
import { verifyPassword } from '../services/hash'
export async function authMiddleware(req: Request, res: Response, next: NextFunction) { export async function authMiddleware(
const secret = req.headers['mixan-client-secret'] as string | undefined req: Request,
res: Response,
next: NextFunction
) {
try {
const clientId = req.headers['mixan-client-id'] as string | undefined
const clientSecret = req.headers['mixan-client-secret'] as string | undefined
if(!secret) { if (!clientId) {
return next(createError(401, 'Misisng client secret')) return next(createError(401, 'Misisng client id'))
}
if (!clientSecret) {
return next(createError(401, 'Misisng client secret'))
}
const client = await db.client.findUnique({
where: {
id: clientId,
},
})
if(!client) {
return next(createError(401, 'Invalid client id'))
}
if (!await verifyPassword(clientSecret, client.secret)) {
return next(createError(401, 'Invalid client secret'))
}
req.client = {
project_id: client.project_id,
}
next()
} catch (error) {
next(new HttpError(500, 'Failed verify client credentials'))
} }
const client = await db.client.findFirst({
where: {
secret,
},
})
if(!client) {
return next(createError(401, 'Invalid client secret'))
}
req.client = {
project_id: client.project_id,
}
next()
} }

View File

@@ -1,9 +1,21 @@
import { NextFunction, Request, Response } from 'express' import { NextFunction, Request, Response } from 'express'
import { db } from '../db' import { db } from '../db'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { hashPassword } from '../services/hash'
import { success } from '../responses/success'
export async function setup(req: Request, res: Response, next: NextFunction) { export async function setup(req: Request, res: Response, next: NextFunction) {
try { try {
const counts = await db.$transaction([
db.organization.count(),
db.project.count(),
db.client.count(),
])
if (counts.some((count) => count > 0)) {
return res.json(success('Setup already done'))
}
const organization = await db.organization.create({ const organization = await db.organization.create({
data: { data: {
name: 'Acme Inc.', name: 'Acme Inc.',
@@ -16,20 +28,21 @@ export async function setup(req: Request, res: Response, next: NextFunction) {
organization_id: organization.id, organization_id: organization.id,
}, },
}) })
const secret = uuid()
const client = await db.client.create({ const client = await db.client.create({
data: { data: {
name: 'Acme Website Client', name: 'Acme Website Client',
project_id: project.id, project_id: project.id,
secret: '4bfc4a0b-37e0-4916-b634-95c6a32a2e77', secret: await hashPassword(secret),
}, },
}) })
res.json({ res.json(
organization, success({
project, clientId: client.id,
client, clientSecret: secret,
}) })
)
} catch (error) { } catch (error) {
next(error) next(error)
} }

View File

@@ -0,0 +1,7 @@
export async function hashPassword(password: string) {
return await Bun.password.hash(password);
}
export async function verifyPassword(password: string, hashedPassword: string) {
return await Bun.password.verify(password, hashedPassword);
}

View File

@@ -8,6 +8,7 @@ import {
type MixanOptions = { type MixanOptions = {
url: string url: string
clientId: string
clientSecret: string clientSecret: string
batchInterval?: number batchInterval?: number
maxBatchSize?: number maxBatchSize?: number
@@ -19,11 +20,13 @@ type MixanOptions = {
class Fetcher { class Fetcher {
private url: string private url: string
private clientId: string
private clientSecret: string private clientSecret: string
private logger: (...args: any[]) => void private logger: (...args: any[]) => void
constructor(options: MixanOptions) { constructor(options: MixanOptions) {
this.url = options.url this.url = options.url
this.clientId = options.clientId
this.clientSecret = options.clientSecret this.clientSecret = options.clientSecret
this.logger = options.verbose ? console.log : () => {} this.logger = options.verbose ? console.log : () => {}
} }
@@ -37,6 +40,7 @@ class Fetcher {
this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2)) this.logger(`Mixan request: ${url}`, JSON.stringify(data, null, 2))
return fetch(url, { return fetch(url, {
headers: { headers: {
['mixan-client-id']: this.clientId,
['mixan-client-secret']: this.clientSecret, ['mixan-client-secret']: this.clientSecret,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
@@ -137,6 +141,7 @@ export class Mixan {
} }
event(name: string, properties: Record<string, any>) { event(name: string, properties: Record<string, any>) {
this.logger('Mixan: Queue event', name)
this.eventBatcher.add({ this.eventBatcher.add({
name, name,
properties, properties,
@@ -149,10 +154,10 @@ export class Mixan {
const profileId = this.options.getProfileId() const profileId = this.options.getProfileId()
if(profileId) { if(profileId) {
this.profileId = profileId this.profileId = profileId
this.logger('Use existing ID', this.profileId); this.logger('Mixan: Use existing ID', this.profileId);
} else { } else {
this.profileId = uuid() this.profileId = uuid()
this.logger('Create new ID', this.profileId); this.logger('Mixan: Create new ID', this.profileId);
this.options.saveProfileId(this.profileId) this.options.saveProfileId(this.profileId)
this.fetch.post('/profiles', { this.fetch.post('/profiles', {
id: this.profileId, id: this.profileId,
@@ -162,12 +167,20 @@ export class Mixan {
} }
async setUser(profile: ProfilePayload) { async setUser(profile: ProfilePayload) {
if(!this.profileId) {
return this.logger('Mixan: Set user failed, no profileId');
}
this.logger('Mixan: Set user', profile);
await this.fetch.post(`/profiles/${this.profileId}`, profile, { await this.fetch.post(`/profiles/${this.profileId}`, profile, {
method: 'PUT' method: 'PUT'
}) })
} }
async setUserProperty(name: string, value: any) { async setUserProperty(name: string, value: any) {
if(!this.profileId) {
return this.logger('Mixan: Set user property, no profileId');
}
this.logger('Mixan: Set user property', name, value);
await this.fetch.post(`/profiles/${this.profileId}`, { await this.fetch.post(`/profiles/${this.profileId}`, {
properties: { properties: {
[name]: value, [name]: value,
@@ -177,9 +190,11 @@ export class Mixan {
async increment(name: string, value: number = 1) { async increment(name: string, value: number = 1) {
if (!this.profileId) { if (!this.profileId) {
this.logger('Mixan: Increment failed, no profileId');
return return
} }
this.logger('Mixan: Increment user property', name, value);
await this.fetch.post(`/profiles/${this.profileId}/increment`, { await this.fetch.post(`/profiles/${this.profileId}/increment`, {
name, name,
value, value,
@@ -190,9 +205,11 @@ export class Mixan {
async decrement(name: string, value: number = 1) { async decrement(name: string, value: number = 1) {
if (!this.profileId) { if (!this.profileId) {
this.logger('Mixan: Decrement failed, no profileId');
return return
} }
this.logger('Mixan: Decrement user property', name, value);
await this.fetch.post(`/profiles/${this.profileId}/decrement`, { await this.fetch.post(`/profiles/${this.profileId}/decrement`, {
name, name,
value, value,
@@ -209,8 +226,10 @@ export class Mixan {
} }
clear() { clear() {
this.eventBatcher.flush() this.logger('Mixan: Clear, send remaining events and remove profileId');
this.eventBatcher.send()
this.options.removeProfileId() this.options.removeProfileId()
this.profileId = undefined this.profileId = undefined
this.setAnonymousUser()
} }
} }