mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Update generateApiKey resolver
This commit is contained in:
parent
849adf84d4
commit
a09588d3a7
9 changed files with 67 additions and 37 deletions
|
|
@ -570,12 +570,15 @@ export type GenerateApiKeyError = {
|
|||
};
|
||||
|
||||
export enum GenerateApiKeyErrorCode {
|
||||
BadRequest = 'BAD_REQUEST'
|
||||
AlreadyExists = 'ALREADY_EXISTS',
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type GenerateApiKeyInput = {
|
||||
expiredAt?: InputMaybe<Scalars['Date']>;
|
||||
scope?: InputMaybe<Scalars['String']>;
|
||||
expiresAt: Scalars['Date'];
|
||||
name: Scalars['String'];
|
||||
scopes?: InputMaybe<Array<Scalars['String']>>;
|
||||
};
|
||||
|
||||
export type GenerateApiKeyResult = GenerateApiKeyError | GenerateApiKeySuccess;
|
||||
|
|
|
|||
|
|
@ -500,12 +500,15 @@ type GenerateApiKeyError {
|
|||
}
|
||||
|
||||
enum GenerateApiKeyErrorCode {
|
||||
ALREADY_EXISTS
|
||||
BAD_REQUEST
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
input GenerateApiKeyInput {
|
||||
expiredAt: Date
|
||||
scope: String
|
||||
expiresAt: Date!
|
||||
name: String!
|
||||
scopes: [String!]
|
||||
}
|
||||
|
||||
union GenerateApiKeyResult = GenerateApiKeyError | GenerateApiKeySuccess
|
||||
|
|
|
|||
|
|
@ -4,33 +4,53 @@ import {
|
|||
GenerateApiKeySuccess,
|
||||
MutationGenerateApiKeyArgs,
|
||||
} from '../../generated/graphql'
|
||||
import { generateApiKey } from '../../utils/auth'
|
||||
import { analytics } from '../../utils/analytics'
|
||||
import { env } from '../../env'
|
||||
import { authorized } from '../../utils/helpers'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
import { User } from '../../entity/user'
|
||||
import { ApiKey } from '../../entity/api_key'
|
||||
import { generateApiKey, hashKey } from '../../utils/auth'
|
||||
|
||||
export const generateApiKeyResolver = authorized<
|
||||
GenerateApiKeySuccess,
|
||||
GenerateApiKeyError,
|
||||
MutationGenerateApiKeyArgs
|
||||
>((_, { input: { scope, expiredAt } }, { claims }) => {
|
||||
>(async (_, { input: { name, expiresAt } }, { claims: { uid }, log }) => {
|
||||
try {
|
||||
console.log('generateApiKeyResolver', scope, expiredAt)
|
||||
log.info('generateApiKeyResolver')
|
||||
const user = await getRepository(User).findOneBy({ id: uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [GenerateApiKeyErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const exp = expiredAt ? new Date(expiredAt).getTime() / 1000 : null
|
||||
const apiKey = generateApiKey({
|
||||
iat: new Date().getTime(),
|
||||
scope: scope || 'all',
|
||||
uid: claims.uid,
|
||||
...(exp && { exp }),
|
||||
const existingApiKey = await getRepository(ApiKey).findOneBy({
|
||||
user: { id: uid },
|
||||
name,
|
||||
})
|
||||
if (existingApiKey) {
|
||||
return {
|
||||
errorCodes: [GenerateApiKeyErrorCode.AlreadyExists],
|
||||
}
|
||||
}
|
||||
|
||||
const exp = new Date(expiresAt)
|
||||
const apiKey = generateApiKey()
|
||||
await getRepository(ApiKey).save({
|
||||
user: { id: uid },
|
||||
name,
|
||||
key: hashKey(apiKey),
|
||||
expiresAt: exp,
|
||||
})
|
||||
|
||||
analytics.track({
|
||||
userId: claims.uid,
|
||||
event: 'generate_api_key',
|
||||
userId: uid,
|
||||
event: 'api_key_generated',
|
||||
properties: {
|
||||
scope,
|
||||
expiredAt: exp,
|
||||
name,
|
||||
expiresAt: exp,
|
||||
env: env.server.apiEnv,
|
||||
},
|
||||
})
|
||||
|
|
@ -38,6 +58,7 @@ export const generateApiKeyResolver = authorized<
|
|||
return { apiKey }
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
return { errorCodes: [GenerateApiKeyErrorCode.BadRequest] }
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { env } from '../../env'
|
|||
import { validateUsername } from '../../utils/usernamePolicy'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import { createUser } from '../../services/create_user'
|
||||
import { comparePassword, hashPassword } from '../../utils/auth'
|
||||
import { compareHashedKey, hashKey } from '../../utils/auth'
|
||||
|
||||
export const updateUserResolver = authorized<
|
||||
UpdateUserSuccess,
|
||||
|
|
@ -310,7 +310,7 @@ export const loginResolver: ResolverFn<
|
|||
}
|
||||
|
||||
// check if password is correct
|
||||
const validPassword = comparePassword(password, user.password)
|
||||
const validPassword = compareHashedKey(password, user.password)
|
||||
if (!validPassword) {
|
||||
return { errorCodes: [LoginErrorCode.InvalidCredentials] }
|
||||
}
|
||||
|
|
@ -331,7 +331,7 @@ export const signupResolver: ResolverFn<
|
|||
|
||||
try {
|
||||
// hash password
|
||||
const hashedPassword = hashPassword(password)
|
||||
const hashedPassword = hashKey(password)
|
||||
|
||||
const [user, profile] = await createUser({
|
||||
email,
|
||||
|
|
|
|||
|
|
@ -1412,8 +1412,9 @@ const schema = gql`
|
|||
}
|
||||
|
||||
input GenerateApiKeyInput {
|
||||
scope: String
|
||||
expiredAt: Date
|
||||
name: String!
|
||||
scopes: [String!]
|
||||
expiresAt: Date!
|
||||
}
|
||||
|
||||
union GenerateApiKeyResult = GenerateApiKeySuccess | GenerateApiKeyError
|
||||
|
|
@ -1428,6 +1429,8 @@ const schema = gql`
|
|||
|
||||
enum GenerateApiKeyErrorCode {
|
||||
BAD_REQUEST
|
||||
ALREADY_EXISTS
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
# Query: search
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import * as bcrypt from 'bcryptjs'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import { env } from '../env'
|
||||
import { Claims } from '../resolvers/types'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export const hashPassword = (password: string) => {
|
||||
return bcrypt.hashSync(password, 10)
|
||||
export const hashKey = (key: string, salt = 10) => {
|
||||
return bcrypt.hashSync(key, salt)
|
||||
}
|
||||
|
||||
export const comparePassword = (password: string, hash: string) => {
|
||||
return bcrypt.compareSync(password, hash)
|
||||
export const compareHashedKey = (rawKey: string, hash: string) => {
|
||||
return bcrypt.compareSync(rawKey, hash)
|
||||
}
|
||||
|
||||
export const generateApiKey = (claims: Claims): string => {
|
||||
return jwt.sign(claims, env.server.jwtSecret)
|
||||
export const generateApiKey = (): string => {
|
||||
// TODO: generate random string key
|
||||
return uuidv4()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createTestUser, deleteTestUser } from '../db'
|
||||
import { graphqlRequest, request } from '../util'
|
||||
import { User } from '../../src/entity/user'
|
||||
import { hashPassword } from '../../src/utils/auth'
|
||||
import { hashKey } from '../../src/utils/auth'
|
||||
import 'mocha'
|
||||
|
||||
describe('Sanitize Directive', () => {
|
||||
|
|
@ -12,7 +12,7 @@ describe('Sanitize Directive', () => {
|
|||
let user: User
|
||||
|
||||
before(async () => {
|
||||
const hashedPassword = hashPassword(correctPassword)
|
||||
const hashedPassword = hashKey(correctPassword)
|
||||
user = await createTestUser(username, '', hashedPassword)
|
||||
const res = await request
|
||||
.post('/local/debug/fake-user-login')
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
UpdateUserProfileErrorCode,
|
||||
} from '../../src/generated/graphql'
|
||||
import { User } from '../../src/entity/user'
|
||||
import { hashPassword } from '../../src/utils/auth'
|
||||
import { hashKey } from '../../src/utils/auth'
|
||||
import 'mocha'
|
||||
|
||||
describe('User API', () => {
|
||||
|
|
@ -21,7 +21,7 @@ describe('User API', () => {
|
|||
let anotherUser: User
|
||||
|
||||
before(async () => {
|
||||
const hashedPassword = hashPassword(correctPassword)
|
||||
const hashedPassword = hashKey(correctPassword)
|
||||
// create test user and login
|
||||
user = await createTestUser(username, '', hashedPassword)
|
||||
const res = await request
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ CREATE TABLE omnivore.api_key (
|
|||
scopes text[] NOT NULL DEFAULT '{}',
|
||||
expires_at timestamptz NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
||||
used_at timestamptz
|
||||
used_at timestamptz,
|
||||
UNIQUE (user_id, name)
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
|
|
|||
Loading…
Reference in a new issue