refactor: replace "reputation" term with "credentials"

re #262
This commit is contained in:
cedoor
2023-08-10 15:29:59 +02:00
parent 69e53db6ba
commit 1b4c82c4bd
72 changed files with 406 additions and 489 deletions

View File

@@ -118,20 +118,20 @@
</tr>
<tr>
<td>
<a href="/libs/reputation">
@bandada/reputation
<a href="/libs/credentials">
@bandada/credentials
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@bandada/reputation">
<img src="https://img.shields.io/npm/v/@bandada/reputation.svg?style=flat-square" alt="NPM version" />
<a href="https://npmjs.org/package/@bandada/credentials">
<img src="https://img.shields.io/npm/v/@bandada/credentials.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@bandada/reputation">
<img src="https://img.shields.io/npm/dm/@bandada/reputation.svg?style=flat-square" alt="Downloads" />
<a href="https://npmjs.org/package/@bandada/credentials">
<img src="https://img.shields.io/npm/dm/@bandada/credentials.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
@@ -300,11 +300,11 @@ Below are the ENV variables used by the `api`:
| INFURA_API_KEY | API Key for Infura. This is used for executing blockchain transactions. |
| BACKEND_PRIVATE_KEY | Ethereum wallet private key used for making blockchain transactions. |
| SIWE_STATEMENT | Statement used as a SIWE message. |
| GITHUB_CLIENT_ID | Github client id used for reputation groups. |
| GITHUB_CLIENT_SECRET | Github client secret used for reputation groups. |
| TWITTER_REDIRECT_URI | Twitter redirect URL used for reputation groups. |
| TWITTER_CLIENT_ID | Twitter client id used for reputation groups. |
| TWITTER_CLIENT_SECRET | Twitter client secret used for reputation groups. |
| GITHUB_CLIENT_ID | Github client id used for credential groups. |
| GITHUB_CLIENT_SECRET | Github client secret used for credential groups. |
| TWITTER_REDIRECT_URI | Twitter redirect URL used for credential groups. |
| TWITTER_CLIENT_ID | Twitter client id used for credential groups. |
| TWITTER_CLIENT_SECRET | Twitter client secret used for credential groups. |
## API

View File

@@ -8,7 +8,7 @@
"build": "nest build"
},
"dependencies": {
"@bandada/reputation": "0.12.0",
"@bandada/credentials": "0.12.0",
"@bandada/utils": "0.12.0",
"@ethersproject/hash": "^5.7.0",
"@nestjs/common": "^9.0.0",

View File

@@ -11,7 +11,7 @@ import { AdminsModule } from "./admins/admins.module"
import { AuthModule } from "./auth/auth.module"
import { GroupsModule } from "./groups/groups.module"
import { InvitesModule } from "./invites/invites.module"
import { ReputationModule } from "./reputation/reputation.module"
import { CredentialsModule } from "./credentials/credentials.module"
type DB_TYPE = "mysql" | "sqlite" | "postgres"
@@ -21,7 +21,7 @@ type DB_TYPE = "mysql" | "sqlite" | "postgres"
AdminsModule,
InvitesModule,
GroupsModule,
ReputationModule,
CredentialsModule,
ThrottlerModule.forRoot({
ttl: 60,
limit: 10

View File

@@ -2,22 +2,22 @@ import { Body, Controller, Post } from "@nestjs/common"
import { ApiExcludeController } from "@nestjs/swagger"
import { AddMemberDto } from "./dto/add-member.dto"
import { OAuthStateDto } from "./dto/oauth-state.dto"
import { ReputationService } from "./reputation.service"
import { CredentialsService } from "./credentials.service"
@ApiExcludeController()
@Controller("reputation")
export class ReputationController {
constructor(private readonly reputationService: ReputationService) {}
@Controller("credentials")
export class CredentialsController {
constructor(private readonly credentialsService: CredentialsService) {}
@Post("oauth-state")
async setOAuthState(@Body() dto: OAuthStateDto): Promise<string> {
return this.reputationService.setOAuthState(dto)
return this.credentialsService.setOAuthState(dto)
}
@Post()
async addMemberByReputation(
async addMemberByCredentials(
@Body() dto: AddMemberDto
): Promise<void | any> {
return this.reputationService.addMember(dto.oAuthCode, dto.oAuthState)
return this.credentialsService.addMember(dto.oAuthCode, dto.oAuthState)
}
}

View File

@@ -0,0 +1,19 @@
import { forwardRef, Module } from "@nestjs/common"
import { ScheduleModule } from "@nestjs/schedule"
import { TypeOrmModule } from "@nestjs/typeorm"
import { GroupsModule } from "../groups/groups.module"
import { OAuthAccount } from "./entities/credentials-account.entity"
import { CredentialsController } from "./credentials.controller"
import { CredentialsService } from "./credentials.service"
@Module({
imports: [
ScheduleModule.forRoot(),
forwardRef(() => GroupsModule),
TypeOrmModule.forFeature([OAuthAccount])
],
controllers: [CredentialsController],
providers: [CredentialsService],
exports: [CredentialsService]
})
export class CredentialsModule {}

View File

@@ -1,4 +1,4 @@
import { getProvider, validateReputation } from "@bandada/reputation"
import { getProvider, validateCredentials } from "@bandada/credentials"
import { ScheduleModule } from "@nestjs/schedule"
import { Test } from "@nestjs/testing"
import { TypeOrmModule } from "@nestjs/typeorm"
@@ -7,8 +7,8 @@ import { Member } from "../groups/entities/member.entity"
import { GroupsService } from "../groups/groups.service"
import { Invite } from "../invites/entities/invite.entity"
import { InvitesService } from "../invites/invites.service"
import { ReputationAccount } from "./entities/reputation-account.entity"
import { ReputationService } from "./reputation.service"
import { OAuthAccount } from "./entities/credentials-account.entity"
import { CredentialsService } from "./credentials.service"
jest.mock("@bandada/utils", () => ({
__esModule: true,
@@ -21,7 +21,7 @@ jest.mock("@bandada/utils", () => ({
})
}))
jest.mock("@bandada/reputation", () => ({
jest.mock("@bandada/credentials", () => ({
__esModule: true,
getProvider: jest.fn(() => ({
getAccessToken: () => "123",
@@ -29,12 +29,12 @@ jest.mock("@bandada/reputation", () => ({
id: "id"
})
})),
validateReputation: jest.fn(() => true)
validateCredentials: jest.fn(() => true)
}))
describe("ReputationService", () => {
describe("CredentialsService", () => {
let groupsService: GroupsService
let reputationService: ReputationService
let credentialsService: CredentialsService
let groupId: string
let stateId: string
@@ -46,23 +46,18 @@ describe("ReputationService", () => {
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [Group, Invite, Member, ReputationAccount],
entities: [Group, Invite, Member, OAuthAccount],
synchronize: true
})
}),
TypeOrmModule.forFeature([
Group,
Invite,
Member,
ReputationAccount
]),
TypeOrmModule.forFeature([Group, Invite, Member, OAuthAccount]),
ScheduleModule.forRoot()
],
providers: [GroupsService, InvitesService, ReputationService]
providers: [GroupsService, InvitesService, CredentialsService]
}).compile()
groupsService = await module.resolve(GroupsService)
reputationService = await module.resolve(ReputationService)
credentialsService = await module.resolve(CredentialsService)
const { id } = await groupsService.createGroup(
{
@@ -70,7 +65,7 @@ describe("ReputationService", () => {
description: "This is a description",
treeDepth: 16,
fingerprintDuration: 3600,
reputationCriteria: JSON.stringify({
credentials: JSON.stringify({
id: "GITHUB_FOLLOWERS",
criteria: {
minFollowers: 12
@@ -84,7 +79,7 @@ describe("ReputationService", () => {
})
describe("# setOAuthState", () => {
it("Should throw an error if the group is not a reputation group", async () => {
it("Should throw an error if the group is not a credential group", async () => {
const { id: _groupId } = await groupsService.createGroup(
{
name: "Group2",
@@ -95,19 +90,19 @@ describe("ReputationService", () => {
"admin"
)
const fun = reputationService.setOAuthState({
const fun = credentialsService.setOAuthState({
groupId: _groupId,
memberId: "123",
providerName: "twitter"
})
await expect(fun).rejects.toThrow(
`Group with id '${_groupId}' is not a reputation group`
`Group with id '${_groupId}' is not a credential group`
)
})
it("Should throw an error if the provider is not supported", async () => {
const fun = reputationService.setOAuthState({
const fun = credentialsService.setOAuthState({
groupId,
memberId: "123",
providerName: "reddit"
@@ -119,7 +114,7 @@ describe("ReputationService", () => {
})
it("Should create and save a state id", async () => {
stateId = await reputationService.setOAuthState({
stateId = await credentialsService.setOAuthState({
groupId,
memberId: "123",
providerName: "twitter"
@@ -139,15 +134,15 @@ describe("ReputationService", () => {
} as any)
)
const _stateId = await reputationService.setOAuthState({
const _stateId = await credentialsService.setOAuthState({
groupId,
memberId: "123",
providerName: "twitter"
})
await reputationService.addMember("code", _stateId)
await credentialsService.addMember("code", _stateId)
const fun = reputationService.setOAuthState({
const fun = credentialsService.setOAuthState({
groupId,
memberId: "123",
providerName: "twitter"
@@ -161,13 +156,13 @@ describe("ReputationService", () => {
describe("# addMember", () => {
it("Should throw an error if the OAuth does not exist", async () => {
const fun = reputationService.addMember("code", "123")
const fun = credentialsService.addMember("code", "123")
await expect(fun).rejects.toThrow(`OAuth state does not exist`)
})
it("Should add a member to a reputation group", async () => {
const clientRedirectUri = await reputationService.addMember(
it("Should add a member to a credential group", async () => {
const clientRedirectUri = await credentialsService.addMember(
"code",
stateId
)
@@ -176,20 +171,20 @@ describe("ReputationService", () => {
})
it("Should throw an error if the same OAuth account tries to join the same group", async () => {
const _stateId = await reputationService.setOAuthState({
const _stateId = await credentialsService.setOAuthState({
groupId,
memberId: "124",
providerName: "twitter"
})
const fun = reputationService.addMember("code", _stateId)
const fun = credentialsService.addMember("code", _stateId)
await expect(fun).rejects.toThrow(
`OAuth account has already joined the group`
)
})
it("Should throw an error if the OAuth account does not have enough reputation", async () => {
it("Should throw an error if the OAuth account does not have enough credential", async () => {
;(getProvider as any).mockImplementationOnce(
() =>
({
@@ -199,20 +194,20 @@ describe("ReputationService", () => {
}))
} as any)
)
;(validateReputation as any).mockImplementationOnce(
;(validateCredentials as any).mockImplementationOnce(
async () => false
)
const _stateId = await reputationService.setOAuthState({
const _stateId = await credentialsService.setOAuthState({
groupId,
memberId: "124",
providerName: "twitter"
})
const fun = reputationService.addMember("code", _stateId)
const fun = credentialsService.addMember("code", _stateId)
await expect(fun).rejects.toThrow(
`OAuth account does not match reputation criteria`
`OAuth account does not match credential criteria`
)
})
})

View File

@@ -1,4 +1,4 @@
import { validateReputation, getProvider } from "@bandada/reputation"
import { validateCredentials, getProvider } from "@bandada/credentials"
import { id } from "@ethersproject/hash"
import {
BadRequestException,
@@ -11,16 +11,16 @@ import { InjectRepository } from "@nestjs/typeorm"
import { Repository } from "typeorm"
import { v4 } from "uuid"
import { GroupsService } from "../groups/groups.service"
import { ReputationAccount } from "./entities/reputation-account.entity"
import { OAuthAccount } from "./entities/credentials-account.entity"
import { OAuthState } from "./types"
@Injectable()
export class ReputationService {
export class CredentialsService {
private oAuthState: Map<string, OAuthState>
constructor(
@InjectRepository(ReputationAccount)
private readonly reputationAccountRepository: Repository<ReputationAccount>,
@InjectRepository(OAuthAccount)
private readonly oAuthAccountRepository: Repository<OAuthAccount>,
@Inject(forwardRef(() => GroupsService))
private readonly groupsService: GroupsService
) {
@@ -35,9 +35,9 @@ export class ReputationService {
async setOAuthState(oAuthState: OAuthState): Promise<string> {
const group = await this.groupsService.getGroup(oAuthState.groupId)
if (!group.reputationCriteria) {
if (!group.credentials) {
throw new BadRequestException(
`Group with id '${oAuthState.groupId}' is not a reputation group`
`Group with id '${oAuthState.groupId}' is not a credential group`
)
}
@@ -66,7 +66,7 @@ export class ReputationService {
}
/**
* Add a member to the reputation group if they meet the right reputation criteria.
* Add a member to the credential group if they meet the right credential criteria.
* @param oAuthCode OAuth code to exchange for an access token.
* @param OAuthState OAuth state to prevent forgery attacks.
* @returns Redirect URI
@@ -107,7 +107,7 @@ export class ReputationService {
const accountHash = id(profile.id + groupId)
if (
group.reputationAccounts.find(
group.oAuthAccounts.find(
(account) => account.accountHash === accountHash
)
) {
@@ -116,26 +116,26 @@ export class ReputationService {
)
}
// Check reputation.
// Check credentials.
if (
!(await validateReputation(JSON.parse(group.reputationCriteria), {
!(await validateCredentials(JSON.parse(group.credentials), {
profile,
accessTokens: { [providerName]: accessToken }
}))
) {
throw new UnauthorizedException(
"OAuth account does not match reputation criteria"
"OAuth account does not match credentials"
)
}
// Save OAuth account to prevent the same account to join groups with
// different member ids.
const reputationAccount = new ReputationAccount()
const oAuthAccount = new OAuthAccount()
reputationAccount.group = group
reputationAccount.accountHash = accountHash
oAuthAccount.group = group
oAuthAccount.accountHash = accountHash
await this.reputationAccountRepository.save(reputationAccount)
await this.oAuthAccountRepository.save(oAuthAccount)
await this.groupsService.addMember(groupId, memberId)
this.oAuthState.delete(oAuthState)

View File

@@ -8,14 +8,14 @@ import {
} from "typeorm"
import { Group } from "../../groups/entities/group.entity"
@Entity("reputation_accounts")
@Entity("oauth_accounts")
@Index(["accountHash", "group"])
@Unique(["accountHash", "group"])
export class ReputationAccount {
export class OAuthAccount {
@PrimaryColumn()
accountHash: string
@ManyToOne(() => Group, (group) => group.reputationAccounts, {
@ManyToOne(() => Group, (group) => group.oAuthAccounts, {
onDelete: "CASCADE"
})
@JoinColumn({ name: "group_id" })

View File

@@ -18,5 +18,5 @@ export class Group {
@ApiProperty({ isArray: true })
members: string
@ApiProperty()
reputationCriteria: object
credentials: object
}

View File

@@ -14,7 +14,7 @@ export class GroupResponse {
@ApiProperty()
fingerprintDuration: number
@ApiProperty()
reputationCriteria: object
credentials: object
@ApiProperty()
apiEnabled: boolean
@ApiProperty()

View File

@@ -38,5 +38,5 @@ export class CreateGroupDto {
@IsJSON()
@IsOptional()
readonly reputationCriteria?: any
readonly credentials?: any
}

View File

@@ -31,5 +31,5 @@ export class UpdateGroupDto {
@IsOptional()
@IsJSON()
readonly reputationCriteria?: any
readonly credentials?: any
}

View File

@@ -7,7 +7,7 @@ import {
PrimaryColumn,
UpdateDateColumn
} from "typeorm"
import { ReputationAccount } from "../../reputation/entities/reputation-account.entity"
import { OAuthAccount } from "../../credentials/entities/credentials-account.entity"
import { Member } from "./member.entity"
@Entity("groups")
@@ -36,17 +36,17 @@ export class Group {
})
members: Member[]
@OneToMany(() => ReputationAccount, (account) => account.group, {
@OneToMany(() => OAuthAccount, (account) => account.group, {
cascade: ["insert"]
})
reputationAccounts: ReputationAccount[]
oAuthAccounts: OAuthAccount[]
@Column({
type: "simple-json",
name: "reputation_criteria",
name: "credentials",
nullable: true
})
reputationCriteria: any // TODO: Add correct type for reputationCriteria JSON
credentials: any // TODO: Add correct type for credentials JSON
@Column({ name: "api_enabled", default: false })
apiEnabled: boolean

View File

@@ -3,7 +3,7 @@ import { Test } from "@nestjs/testing"
import { TypeOrmModule } from "@nestjs/typeorm"
import { Invite } from "../invites/entities/invite.entity"
import { InvitesService } from "../invites/invites.service"
import { ReputationAccount } from "../reputation/entities/reputation-account.entity"
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
import { Group } from "./entities/group.entity"
import { Member } from "./entities/member.entity"
import { GroupsService } from "./groups.service"
@@ -33,7 +33,7 @@ describe("GroupsService", () => {
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [Group, Invite, Member, ReputationAccount],
entities: [Group, Invite, Member, OAuthAccount],
synchronize: true
})
}),
@@ -112,7 +112,7 @@ describe("GroupsService", () => {
description: "This is a description",
treeDepth: 16,
fingerprintDuration: 3600,
reputationCriteria: {
credentials: {
id: "GITHUB_FOLLOWERS",
criteria: {
minFollowers: 12
@@ -122,13 +122,13 @@ describe("GroupsService", () => {
"admin"
)
const { description, fingerprintDuration, reputationCriteria } =
const { description, fingerprintDuration, credentials } =
await groupsService.updateGroup(
id,
{
description: "This is a new description",
fingerprintDuration: 1000,
reputationCriteria: {
credentials: {
id: "TWITTER_FOLLOWERS",
minFollowers: 23
}
@@ -138,7 +138,7 @@ describe("GroupsService", () => {
expect(description).toContain("new")
expect(fingerprintDuration).toBe(1000)
expect(reputationCriteria.id).toBe("TWITTER_FOLLOWERS")
expect(credentials.id).toBe("TWITTER_FOLLOWERS")
})
it("Should not update a group if the admin is the wrong one", async () => {

View File

@@ -63,7 +63,7 @@ export class GroupsService {
description,
treeDepth,
fingerprintDuration,
reputationCriteria
credentials
}: CreateGroupDto,
adminId: string
): Promise<Group> {
@@ -79,7 +79,7 @@ export class GroupsService {
description,
treeDepth,
fingerprintDuration,
reputationCriteria,
credentials,
adminId,
members: []
})
@@ -133,7 +133,7 @@ export class GroupsService {
description,
treeDepth,
apiEnabled,
reputationCriteria,
credentials,
fingerprintDuration
}: UpdateGroupDto,
adminId: string
@@ -166,11 +166,11 @@ export class GroupsService {
this._updateContractGroup(cachedGroup)
}
if (group.reputationCriteria && reputationCriteria) {
group.reputationCriteria = reputationCriteria
if (group.credentials && credentials) {
group.credentials = credentials
}
if (!group.reputationCriteria && apiEnabled !== undefined) {
if (!group.credentials && apiEnabled !== undefined) {
group.apiEnabled = apiEnabled
// Generate a new API key if it doesn't exist
@@ -437,7 +437,7 @@ export class GroupsService {
*/
async getGroup(groupId: string): Promise<Group> {
const group = await this.groupRepository.findOne({
relations: { members: true, reputationAccounts: true },
relations: { members: true, oAuthAccounts: true },
where: { id: groupId }
})

View File

@@ -13,7 +13,7 @@ export function mapGroupToResponseDTO(
fingerprintDuration: group.fingerprintDuration,
createdAt: group.createdAt,
members: (group.members || []).map((m) => m.id),
reputationCriteria: group.reputationCriteria,
credentials: group.credentials,
apiKey: undefined,
apiEnabled: undefined
}

View File

@@ -4,7 +4,7 @@ import { TypeOrmModule } from "@nestjs/typeorm"
import { Group } from "../groups/entities/group.entity"
import { Member } from "../groups/entities/member.entity"
import { GroupsService } from "../groups/groups.service"
import { ReputationAccount } from "../reputation/entities/reputation-account.entity"
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
import { Invite } from "./entities/invite.entity"
import { InvitesService } from "./invites.service"
@@ -32,7 +32,7 @@ describe("InvitesService", () => {
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [Group, Invite, Member, ReputationAccount],
entities: [Group, Invite, Member, OAuthAccount],
synchronize: true
})
}),
@@ -77,14 +77,14 @@ describe("InvitesService", () => {
await expect(fun).rejects.toThrow("You are not the admin")
})
it("Should not create an invite if the group is a reputation group", async () => {
it("Should not create an invite if the group is a credential group", async () => {
const group = await groupsService.createGroup(
{
name: "Group2",
description: "This is a description",
treeDepth: 16,
fingerprintDuration: 3600,
reputationCriteria: {
credentials: {
id: "GITHUB_FOLLOWERS",
criteria: {
minFollowers: 12
@@ -100,7 +100,7 @@ describe("InvitesService", () => {
)
await expect(fun).rejects.toThrow(
"Reputation groups cannot be accessed via invites"
"Credential groups cannot be accessed via invites"
)
})
})

View File

@@ -40,9 +40,9 @@ export class InvitesService {
)
}
if (group.reputationCriteria) {
if (group.credentials) {
throw new UnauthorizedException(
"Reputation groups cannot be accessed via invites"
"Credential groups cannot be accessed via invites"
)
}

View File

@@ -1,19 +0,0 @@
import { forwardRef, Module } from "@nestjs/common"
import { ScheduleModule } from "@nestjs/schedule"
import { TypeOrmModule } from "@nestjs/typeorm"
import { GroupsModule } from "../groups/groups.module"
import { ReputationAccount } from "./entities/reputation-account.entity"
import { ReputationController } from "./reputation.controller"
import { ReputationService } from "./reputation.service"
@Module({
imports: [
ScheduleModule.forRoot(),
forwardRef(() => GroupsModule),
TypeOrmModule.forFeature([ReputationAccount])
],
controllers: [ReputationController],
providers: [ReputationService],
exports: [ReputationService]
})
export class ReputationModule {}

View File

@@ -1,8 +1,9 @@
# This file contains build time variables for dev env.
VITE_API_URL=http://localhost:3000
VITE_CLIENT_URL=http://localhost:3002
VITE_CLIENT_INVITES_URL=http://localhost:3002?inviteCode=\
VITE_ETHEREUM_NETWORK=goerli
VITE_GITHUB_CLIENT_ID=a83a8b014ef38270fb22
VITE_TWITTER_CLIENT_ID=NV82Mm85NWlSZ1llZkpLMl9vN3A6MTpjaQ
VITE_TWITTER_REDIRECT_URI=http://localhost:3001/reputation
VITE_TWITTER_REDIRECT_URI=http://localhost:3001/credentials

View File

@@ -1,8 +1,9 @@
# This file contains build time variables for prod env.
VITE_API_URL=https://api.bandada.pse.dev
VITE_CLIENT_URL=https://client.bandada.pse.dev
VITE_CLIENT_INVITES_URL=https://client.bandada.pse.dev?inviteCode=\
VITE_ETHEREUM_NETWORK=goerli
VITE_GITHUB_CLIENT_ID=6ccd7b93e84260e353f9
VITE_TWITTER_CLIENT_ID=NV82Mm85NWlSZ1llZkpLMl9vN3A6MTpjaQ
VITE_TWITTER_REDIRECT_URI=https://bandada.pse.dev/reputation
VITE_TWITTER_REDIRECT_URI=https://bandada.pse.dev/credentials

View File

@@ -8,7 +8,7 @@
"preview": "vite preview"
},
"dependencies": {
"@bandada/reputation": "0.12.0",
"@bandada/credentials": "0.12.0",
"@bandada/utils": "0.12.0",
"@chakra-ui/react": "^2.5.1",
"@chakra-ui/theme-tools": "^2.0.16",

View File

@@ -101,7 +101,7 @@ export async function createGroup(
description: string,
treeDepth: number,
fingerprintDuration: number,
reputationCriteria?: any
credentials?: any
): Promise<Group | null> {
try {
const group = await request(`${API_URL}/groups`, {
@@ -111,7 +111,7 @@ export async function createGroup(
description,
treeDepth,
fingerprintDuration,
reputationCriteria: JSON.stringify(reputationCriteria)
credentials: JSON.stringify(credentials)
}
})
@@ -205,7 +205,7 @@ export async function removeGroup(groupId: string): Promise<void | null> {
/**
* It returns a random string to be used as a OAuth state, to to protect against
* forgery attacks. It will be used to retrieve group, member, redirectURI and provider
* before checking reputation and adding members.
* before checking credentials and adding members.
* @param group The group id.
* @param memberId The group member id.
* @param redirectUri The URL where clients will be sent after authorization.
@@ -219,7 +219,7 @@ export async function setOAuthState(
redirectUri?: string
): Promise<string | null> {
try {
return await request(`${API_URL}/reputation/oauth-state`, {
return await request(`${API_URL}/credentials/oauth-state`, {
method: "POST",
data: {
groupId,
@@ -242,16 +242,16 @@ export async function setOAuthState(
}
/**
* It adds a new member to an existing reputation group.
* It adds a new member to an existing credentials group.
* @param oAuthState The OAuth state.
* @param oAuthCode The OAuth code.
*/
export async function addMemberByReputation(
export async function addMemberByCredentials(
oAuthState: string,
oAuthCode: string
): Promise<string | null> {
try {
return await request(`${API_URL}/reputation`, {
return await request(`${API_URL}/credentials`, {
method: "POST",
data: {
oAuthCode,

View File

@@ -3,7 +3,6 @@ import {
AbsoluteCenter,
Box,
Button,
Code,
Divider,
Heading,
Icon,
@@ -40,15 +39,23 @@ export default function AddMemberModal({
const [_isLoading, setIsLoading] = useState(false)
const {
hasCopied,
value: _inviteLink,
setValue: setInviteLink,
value: _clientLink,
setValue: setClientLink,
onCopy
} = useClipboard("")
const { data: signer } = useSigner()
useEffect(() => {
setMemberId("")
}, [group, isOpen, setInviteLink])
if (group.credentials) {
setClientLink(
`${import.meta.env.VITE_CLIENT_URL}?credentialGroupId=${
group.id
}`
)
}
}, [group, setClientLink])
const addMember = useCallback(async () => {
if (!_memberId) {
@@ -105,8 +112,8 @@ export default function AddMemberModal({
return
}
setInviteLink(inviteLink)
}, [group, setInviteLink])
setClientLink(inviteLink)
}, [group, setClientLink])
return (
<Modal
@@ -122,7 +129,7 @@ export default function AddMemberModal({
New member
</Heading>
{!group.reputationCriteria && (
{!group.credentials && (
<Box mb="5px">
<Text my="10px" color="balticSea.800">
Add member ID
@@ -150,85 +157,75 @@ export default function AddMemberModal({
</Box>
)}
{group.type === "off-chain" &&
!group.reputationCriteria && (
<>
<Box position="relative" py="8">
<Divider borderColor="balticSea.300" />
<AbsoluteCenter
fontSize="13px"
px="4"
bgColor="balticSea.50"
>
OR
</AbsoluteCenter>
</Box>
{group.type === "off-chain" && !group.credentials && (
<Box position="relative" py="8">
<Divider borderColor="balticSea.300" />
<AbsoluteCenter
fontSize="13px"
px="4"
bgColor="balticSea.50"
>
OR
</AbsoluteCenter>
</Box>
)}
<Box mb="30px">
<Text mb="10px" color="balticSea.800">
Share invite link
</Text>
<InputGroup size="lg">
<Input
pr="50px"
placeholder="Invite link"
value={_inviteLink}
isDisabled
/>
<InputRightElement mr="5px">
<Tooltip
label={
hasCopied
? "Copied!"
: "Copy"
}
closeOnClick={false}
hasArrow
>
<IconButton
variant="link"
aria-label="Copy invite link"
onClick={onCopy}
onMouseDown={(e) =>
e.preventDefault()
}
icon={
<Icon
color="sunsetOrange.600"
boxSize="5"
as={FiCopy}
/>
}
/>
</Tooltip>
</InputRightElement>
</InputGroup>
<Button
mt="10px"
variant="link"
color="balticSea.600"
textDecoration="underline"
onClick={generateInviteLink}
>
Generate new link
</Button>
</Box>
</>
)}
{group.reputationCriteria && (
<>
<Text mb="10px">
To allow users to join your group, you can use
the following Bandada URL:
{group.type === "off-chain" && (
<Box mb="30px">
<Text mb="10px" color="balticSea.800">
{!group.credentials
? "Share invite link"
: "Share access link"}
</Text>
<Code
p="3"
mb="20px"
>{`${window.location.origin}/reputation?group=<groupID>&member=<memberID>&provider=<providerName>&redirect_uri=<redirectURI>`}</Code>
</>
<InputGroup size="lg">
<Input
pr="50px"
placeholder={
!group.credentials
? "Invite link"
: "Access link"
}
value={_clientLink}
isDisabled
/>
<InputRightElement mr="5px">
<Tooltip
label={hasCopied ? "Copied!" : "Copy"}
closeOnClick={false}
hasArrow
>
<IconButton
variant="link"
aria-label="Copy invite link"
onClick={onCopy}
onMouseDown={(e) =>
e.preventDefault()
}
icon={
<Icon
color="sunsetOrange.600"
boxSize="5"
as={FiCopy}
/>
}
/>
</Tooltip>
</InputRightElement>
</InputGroup>
{!group.credentials && (
<Button
mt="10px"
variant="link"
color="balticSea.600"
textDecoration="underline"
onClick={generateInviteLink}
>
Generate new link
</Button>
)}
</Box>
)}
<Button

View File

@@ -1,4 +1,4 @@
import { validators } from "@bandada/reputation"
import { validators } from "@bandada/credentials"
import {
Box,
Button,
@@ -38,10 +38,10 @@ export default function AccessModeStep({
}: AccessModeStepProps): JSX.Element {
const [_accessMode, setAccessMode] = useState<AccessMode>("manual")
const [_validator, setValidator] = useState<number>(0)
const [_reputationCriteria, setReputationCriteria] = useState<any>()
const [_credentials, setCredentials] = useState<any>()
useEffect(() => {
setReputationCriteria({
setCredentials({
id: validators[_validator].id,
criteria: {}
})
@@ -49,9 +49,9 @@ export default function AccessModeStep({
useEffect(() => {
if (_accessMode === "manual") {
setReputationCriteria(undefined)
setCredentials(undefined)
} else {
setReputationCriteria({
setCredentials({
id: validators[_validator].id,
criteria: {}
})
@@ -154,8 +154,8 @@ export default function AccessModeStep({
</Select>
</VStack>
{_reputationCriteria &&
_reputationCriteria.criteria &&
{_credentials &&
_credentials.criteria &&
Object.entries(validators[_validator].criteriaABI).map(
(parameter) => (
<VStack
@@ -169,15 +169,15 @@ export default function AccessModeStep({
<NumberInput
size="lg"
value={
_reputationCriteria.criteria[
_credentials.criteria[
parameter[0]
]
}
onChange={(value) =>
setReputationCriteria({
..._reputationCriteria,
setCredentials({
..._credentials,
criteria: {
..._reputationCriteria.criteria,
..._credentials.criteria,
[parameter[0]]:
Number(value)
}
@@ -194,15 +194,15 @@ export default function AccessModeStep({
<Input
size="lg"
value={
_reputationCriteria.criteria[
_credentials.criteria[
parameter[0]
]
}
onChange={(event) =>
setReputationCriteria({
..._reputationCriteria,
setCredentials({
..._credentials,
criteria: {
..._reputationCriteria.criteria,
..._credentials.criteria,
[parameter[0]]:
event.target.value
}
@@ -223,7 +223,7 @@ export default function AccessModeStep({
>
Disclaimer: We will use a bit of your members data
to check if they meet the criteria and generate
their reputation to join the group.
their credentials to join the group.
</Text>
</Box>
</>
@@ -237,23 +237,22 @@ export default function AccessModeStep({
isDisabled={
!_accessMode ||
(_accessMode === "credentials" &&
(!_reputationCriteria ||
!_reputationCriteria.criteria ||
Object.keys(_reputationCriteria.criteria)
.length !==
(!_credentials ||
!_credentials.criteria ||
Object.keys(_credentials.criteria).length !==
Object.keys(
validators[_validator].criteriaABI
).length ||
Object.values(
_reputationCriteria.criteria
).some((c) => c === undefined)))
Object.values(_credentials.criteria).some(
(c) => c === undefined
)))
}
variant="solid"
colorScheme="primary"
onClick={() => {
onSubmit({
...group,
reputationCriteria: _reputationCriteria
credentials: _credentials
})
}}
>

View File

@@ -42,7 +42,7 @@ export default function FinalPreviewStep({
group.description,
group.treeDepth,
group.fingerprintDuration,
group.reputationCriteria
group.credentials
)
if (response === null) {

View File

@@ -234,84 +234,80 @@ export default function GroupPage(): JSX.Element {
</Box>
</HStack>
{groupType === "off-chain" &&
!_group.reputationCriteria && (
<Box
bgColor="balticSea.50"
p="25px 30px 25px 30px"
borderRadius="8px"
>
<HStack justify="space-between">
<Text fontSize="20px">Use API key</Text>
{groupType === "off-chain" && !_group.credentials && (
<Box
bgColor="balticSea.50"
p="25px 30px 25px 30px"
borderRadius="8px"
>
<HStack justify="space-between">
<Text fontSize="20px">Use API key</Text>
<Switch
id="enable-api"
isChecked={_group.apiEnabled}
onChange={(event) =>
onApiAccessToggle(
event.target.checked
)
}
/>
</HStack>
<Switch
id="enable-api"
isChecked={_group.apiEnabled}
onChange={(event) =>
onApiAccessToggle(event.target.checked)
}
/>
</HStack>
<Text mt="10px" color="balticSea.700">
Connect your app to your group using an API
key.
</Text>
<Text mt="10px" color="balticSea.700">
Connect your app to your group using an API key.
</Text>
{_group.apiEnabled && (
<>
<InputGroup size="lg" mt="10px">
<Input
pr="50px"
placeholder="API key"
value={_group?.apiKey}
isDisabled
/>
{_group.apiEnabled && (
<>
<InputGroup size="lg" mt="10px">
<Input
pr="50px"
placeholder="API key"
value={_group?.apiKey}
isDisabled
/>
<InputRightElement mr="5px">
<Tooltip
label={
hasCopied
? "Copied!"
: "Copy"
<InputRightElement mr="5px">
<Tooltip
label={
hasCopied
? "Copied!"
: "Copy"
}
closeOnClick={false}
hasArrow
>
<IconButton
variant="link"
aria-label="Copy invite link"
onClick={onCopy}
onMouseDown={(e) =>
e.preventDefault()
}
closeOnClick={false}
hasArrow
>
<IconButton
variant="link"
aria-label="Copy invite link"
onClick={onCopy}
onMouseDown={(e) =>
e.preventDefault()
}
icon={
<Icon
color="sunsetOrange.600"
boxSize="5"
as={FiCopy}
/>
}
/>
</Tooltip>
</InputRightElement>
</InputGroup>
icon={
<Icon
color="sunsetOrange.600"
boxSize="5"
as={FiCopy}
/>
}
/>
</Tooltip>
</InputRightElement>
</InputGroup>
<Button
mt="10px"
variant="link"
color="balticSea.600"
textDecoration="underline"
onClick={generateApiKey}
>
Generate new key
</Button>
</>
)}
</Box>
)}
<Button
mt="10px"
variant="link"
color="balticSea.600"
textDecoration="underline"
onClick={generateApiKey}
>
Generate new key
</Button>
</>
)}
</Box>
)}
<Image src={image1} />

View File

@@ -1,72 +0,0 @@
import { getProvider } from "@bandada/reputation"
import { Flex, Text } from "@chakra-ui/react"
import { useEffect, useState } from "react"
import { useSearchParams } from "react-router-dom"
import { addMemberByReputation, setOAuthState } from "../api/bandadaAPI"
export default function ReputationPage() {
const [_searchParams] = useSearchParams()
const [_message, setMessage] = useState<string>(
"Joining reputation group..."
)
useEffect(() => {
;(async () => {
if (_searchParams.has("group") && _searchParams.has("member")) {
const groupId = _searchParams.get("group")
const memberId = _searchParams.get("member")
const providerName = _searchParams.get("provider")
const clientRedirectUri =
_searchParams.get("redirect_uri") || undefined
const state = await setOAuthState(
groupId as string,
memberId as string,
providerName as string,
clientRedirectUri
)
if (state) {
const clientId = import.meta.env[
`VITE_${providerName?.toUpperCase()}_CLIENT_ID`
]
const redirectUri = import.meta.env[
`VITE_${providerName?.toUpperCase()}_REDIRECT_URI`
]
const provider = getProvider(providerName as string)
const authUrl = provider.getAuthUrl(
clientId,
state,
redirectUri
)
window.location.replace(authUrl)
}
}
if (_searchParams.has("code") && _searchParams.has("state")) {
const oAuthCode = _searchParams.get("code") as string
const oAuthState = _searchParams.get("state") as string
const clientRedirectUri = await addMemberByReputation(
oAuthState,
oAuthCode
)
if (clientRedirectUri) {
window.location.replace(clientRedirectUri)
} else {
setMessage("You have joined the group!")
}
}
})()
}, [_searchParams])
return (
<Flex flex="1" justify="center" align="center">
<Text>{_message}</Text>
</Flex>
)
}

View File

@@ -7,7 +7,7 @@ import GroupPage from "./pages/group"
import GroupsPage from "./pages/groups"
import HomePage from "./pages/home"
import NewGroupPage from "./pages/new-group"
import ReputationPage from "./pages/reputation"
import CredentialsPage from "./pages/credentials"
export default function Routes(): JSX.Element {
const { admin } = useContext(AuthContext)
@@ -51,8 +51,8 @@ export default function Routes(): JSX.Element {
]
},
{
path: "reputation",
element: <ReputationPage />
path: "credentials",
element: <CredentialsPage />
}
]),
[admin]

View File

@@ -4,7 +4,7 @@ export type Group = {
type?: string
description?: string
treeDepth: number
reputationCriteria?: string
credentials?: string
fingerprintDuration?: number
members: string[]
admin: string

View File

@@ -16,7 +16,7 @@ CREATE TABLE groups (
admin_id character varying NOT NULL,
tree_depth integer NOT NULL,
fingerprint_duration integer NOT NULL,
reputation_criteria text,
credentials text,
api_enabled boolean NOT NULL DEFAULT false,
api_key character varying,
created_at timestamp without time zone NOT NULL DEFAULT now(),
@@ -34,7 +34,7 @@ CREATE TABLE members (
-- Table Definition ----------------------------------------------
CREATE TABLE reputation_accounts (
CREATE TABLE oauth_accounts (
"accountHash" character varying PRIMARY KEY,
group_id character varying(32) REFERENCES groups(id),
CONSTRAINT "UQ_f053a0e63cc508da7d0eccc677b" UNIQUE ("accountHash", group_id)
@@ -53,4 +53,4 @@ CREATE TABLE invites (
ALTER TABLE "groups" ADD FOREIGN KEY ("admin_id") REFERENCES "admins" ("id");
ALTER TABLE "members" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
ALTER TABLE "invites" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
ALTER TABLE "reputation_accounts" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");
ALTER TABLE "oauth_accounts" ADD FOREIGN KEY ("group_id") REFERENCES "groups" ("id");

View File

@@ -33,7 +33,7 @@ describe("Bandada API SDK", () => {
fingerprintDuration: 3600,
createdAt: "2023-07-15T08:21:05.000Z",
members: [],
reputationCriteria: null
credentials: null
}
])
)
@@ -54,7 +54,7 @@ describe("Bandada API SDK", () => {
fingerprintDuration: 3600,
createdAt: "2023-07-15T08:21:05.000Z",
members: [],
reputationCriteria: null
credentials: null
})
)
@@ -184,7 +184,7 @@ describe("Bandada API SDK", () => {
"0x63229164c457584616006e31d1e171e6cdd4163695bc9c4bf0227095998ffa4c",
treeDepth: 16,
fingerprintDuration: 3600,
reputationCriteria: null,
credentials: null,
apiEnabled: false,
apiKey: null,
createdAt: "2023-08-09T18:09:53.000Z",

View File

@@ -7,7 +7,7 @@ export type GroupResponse = {
fingerprintDuration: number
createdAt: Date
members: string[]
reputationCriteria: object
credentials: object
}
type Group = {
@@ -17,7 +17,7 @@ type Group = {
adminId: string
treeDepth: number
fingerprintDuration: number
reputationCriteria: object
credentials: object
apiEnabled: boolean
apiKey: string
createdAt: Date

View File

@@ -1,8 +1,8 @@
<p align="center">
<h1 align="center">
Bandada reputation
Bandada credentials
</h1>
<p align="center">Bandada library to validate users' reputation.</p>
<p align="center">Bandada library to validate users' credentials.</p>
</p>
<p align="center">
@@ -12,11 +12,11 @@
<a href="https://github.com/privacy-scaling-explorations/bandada/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/privacy-scaling-explorations/bandada.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@bandada/reputation">
<img alt="NPM version" src="https://img.shields.io/npm/v/@bandada/reputation?style=flat-square" />
<a href="https://www.npmjs.com/package/@bandada/credentials">
<img alt="NPM version" src="https://img.shields.io/npm/v/@bandada/credentials?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@bandada/reputation">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@bandada/reputation.svg?style=flat-square" />
<a href="https://npmjs.org/package/@bandada/credentials">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@bandada/credentials.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
@@ -46,33 +46,33 @@
</h4>
</div>
| This package provides a function to validate users' reputation by using a set of extendable validators. |
| This package provides a function to validate users' credentials by using a set of extendable validators. |
| ------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@bandada/reputation` package with npm:
Install the `@bandada/credentials` package with npm:
```bash
npm i @bandada/reputation
npm i @bandada/credentials
```
or yarn:
```bash
yarn add @bandada/reputation
yarn add @bandada/credentials
```
## 📜 Usage
\# **validateReputation**(reputationCriteria: _ReputationCriteria_, context: _Context_)
\# **validateCredentials**(credentials: _Credentials_, context: _Context_)
```typescript
import { validateReputation, githubFollowers } from "@bandada/reputation"
import { validateCredentials, githubFollowers } from "@bandada/credentials"
validateReputation(
validateCredentials(
{
id: githubFollowers.id,
criteria: {
@@ -92,11 +92,11 @@ validateReputation(
The library has been built to allow external devs to add their own validators. A validator is a simple file that exports 3 JavaScript values:
1. `id`: The validater id. It must be unique and capitalized (snake case).
2. `criteriaABI`: The criteria ABI. It contains the structure of your reputation criteria with its types.
3. `validate`: The validator handler. It usually consists of three steps: criteria types check, user data retrieval and reputation validation.
2. `criteriaABI`: The criteria ABI. It contains the structure of your criteria with its types.
3. `validate`: The validator handler. It usually consists of three steps: criteria types check, user data retrieval and credentials' validation.
```typescript
import { Handler } from "@bandada/reputation"
import { Handler } from "@bandada/credentials"
// Typescript type for the handler criteria.
// This will be mainly used by this handler.
@@ -108,7 +108,7 @@ const validator: Validator = {
id: "GITHUB_FOLLOWERS",
// The criteria application binary interface. It contains
// the structure of this validator reputation criteria
// the structure of this validator credentials
// with its parameter types.
criteriaABI: {
minFollowers: "number"
@@ -116,15 +116,15 @@ const validator: Validator = {
/**
* It checks if a user has more then 'minFollowers' followers.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the credentials.
*/
async validate(criteria: Criteria, { utils }) {
// Step 1: use the API to get the user's parameters.
const { followers } = await utils.api("user")
// Step 2: check if they meet the validator reputation criteria.
// Step 2: check if they meet the validator credentials.
return followers >= criteria.minFollowers
}
}
@@ -138,8 +138,8 @@ Testing your validator is also important. If you use Jest you can use some test
import {
addValidator,
testUtils,
validateReputation
} from "@bandada/reputation"
validateCredentials
} from "@bandada/credentials"
import githubFollowers from "./index"
describe("GithubFollowers", () => {
@@ -152,7 +152,7 @@ describe("GithubFollowers", () => {
followers: 110
})
const result = await validateReputation(
const result = await validateCredentials(
{
id: "GITHUB_FOLLOWERS",
criteria: {

View File

@@ -1,7 +1,7 @@
{
"name": "@bandada/reputation",
"name": "@bandada/credentials",
"version": "0.12.0",
"description": "Bandada library to validate users' reputation.",
"description": "Bandada library to validate users' credentials.",
"license": "MIT",
"main": "dist/index.node.js",
"exports": {
@@ -16,7 +16,7 @@
"README.md"
],
"repository": "https://github.com/privacy-scaling-explorations/bandada",
"homepage": "https://github.com/privacy-scaling-explorations/bandada/tree/main/libs/reputation",
"homepage": "https://github.com/privacy-scaling-explorations/bandada/tree/main/libs/credentials",
"bugs": {
"url": "https://github.com/privacy-scaling-explorations/bandada.git/issues"
},

View File

@@ -2,7 +2,7 @@ import { Validator } from "./types"
import validators from "./validators"
/**
* It adds a new reputation validator.
* It adds a new credential's validator.
* @param validator The validator to be added.
*/
export default function addValidator(validator: Validator) {

View File

@@ -2,7 +2,7 @@ import addValidator from "./addValidator"
import { Validator } from "./types"
/**
* It adds a list of new reputation validators.
* It adds a list of new credential's validators.
* @param validators The list of validators to be added.
*/
export default function addValidators(validators: Validator[]) {

View File

@@ -1,7 +1,7 @@
/**
* It checks if the criteria parameters are the right ones. Criteria must
* contain the right parameters and the right types for each of them.
* @param criteria The reputation criteria to check.
* @param criteria The credentials to check.
* @param criteriaABI The criteria ABI.
*/
export default function checkCriteria(criteria: any, criteriaABI: any) {

View File

@@ -7,7 +7,7 @@ import getValidator from "./getValidator"
import providers from "./providers"
import validators from "./validators"
describe("Reputation library", () => {
describe("Credentials library", () => {
describe("# addProvider", () => {
it("Should add a provider to the list of supported providers", () => {
addProvider({} as any)

View File

@@ -6,14 +6,14 @@ import getProvider from "./getProvider"
import getValidator from "./getValidator"
import providers from "./providers"
import * as testUtils from "./testUtils"
import validateReputation from "./validateReputation"
import validateCredentials from "./validateCredentials"
import validators from "./validators"
export * from "./providers/index"
export * from "./types"
export * from "./validators/index"
export {
validateReputation,
validateCredentials,
addValidator,
getProvider,
getValidator,

View File

@@ -30,7 +30,7 @@ export interface Validator {
validate: Handler
}
export type ReputationCriteria = {
export type Credentials = {
id: string
criteria: any
}

View File

@@ -2,18 +2,18 @@ import checkCriteria from "./checkCriteria"
import getAPI from "./getAPI"
import getProvider from "./getProvider"
import getValidator from "./getValidator"
import { Context, ReputationCriteria } from "./types"
import { Context, Credentials } from "./types"
/**
* It checks if the user meets the reputation criteria of a group.
* It also adds utility functions to the reputation context that
* It checks if the user meets the credentials of a group.
* It also adds utility functions to the credentials context that
* can be used by validators.
* @param reputationCriteria The reputation criteria of a group.
* @param credentials The credentials of a group.
* @param context A set of context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the credentials.
*/
export default async function validateReputation(
{ id, criteria }: ReputationCriteria,
export default async function validateCredentials(
{ id, criteria }: Credentials,
context: Omit<Context, "utils">
): Promise<boolean> {
const validator = getValidator(id)

View File

@@ -1,9 +1,9 @@
import { validateReputation } from "../.."
import { validateCredentials } from "../.."
import githubFollowers from "./index"
describe("GithubFollowers", () => {
it("Should return true if a Github user has more than 100 followers", async () => {
const result = await validateReputation(
const result = await validateCredentials(
{
id: githubFollowers.id,
criteria: {
@@ -23,7 +23,7 @@ describe("GithubFollowers", () => {
it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubFollowers.id,
criteria: {}
@@ -43,7 +43,7 @@ describe("GithubFollowers", () => {
it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubFollowers.id,
criteria: {
@@ -66,7 +66,7 @@ describe("GithubFollowers", () => {
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubFollowers.id,
criteria: {

View File

@@ -13,9 +13,9 @@ const validator: Validator = {
/**
* It checks if a user has more then 'minFollowers' followers.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, { profile }) {
return profile.followers >= criteria.minFollowers

View File

@@ -1,4 +1,4 @@
import { testUtils, validateReputation } from "../.."
import { testUtils, validateCredentials } from "../.."
import githubPersonalStars from "./index"
describe("GithubPersonalStars", () => {
@@ -10,7 +10,7 @@ describe("GithubPersonalStars", () => {
Array.from(Array(20).keys()).map(() => ({ stargazers_count: 10 }))
)
const result = await validateReputation(
const result = await validateCredentials(
{
id: githubPersonalStars.id,
criteria: {
@@ -28,7 +28,7 @@ describe("GithubPersonalStars", () => {
it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubPersonalStars.id,
criteria: {}
@@ -46,7 +46,7 @@ describe("GithubPersonalStars", () => {
it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubPersonalStars.id,
criteria: {
@@ -67,7 +67,7 @@ describe("GithubPersonalStars", () => {
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubPersonalStars.id,
criteria: {

View File

@@ -13,9 +13,9 @@ const validator: Validator = {
/**
* It checks if a user has more then 'minStars' stars in their personal repository.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, { utils }) {
let allRepositories = []

View File

@@ -1,4 +1,4 @@
import { testUtils, validateReputation } from "../.."
import { testUtils, validateCredentials } from "../.."
import githubRepositoryCommits from "./index"
describe("GithubRepositoryCommits", () => {
@@ -6,7 +6,7 @@ describe("GithubRepositoryCommits", () => {
testUtils.mockAPIOnce(Array.from(Array(100).keys()))
testUtils.mockAPIOnce(Array.from(Array(80).keys()))
const result = await validateReputation(
const result = await validateCredentials(
{
id: githubRepositoryCommits.id,
criteria: {
@@ -25,7 +25,7 @@ describe("GithubRepositoryCommits", () => {
it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubRepositoryCommits.id,
criteria: { repository: "hello-worId" }
@@ -43,7 +43,7 @@ describe("GithubRepositoryCommits", () => {
it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubRepositoryCommits.id,
criteria: {
@@ -65,7 +65,7 @@ describe("GithubRepositoryCommits", () => {
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: githubRepositoryCommits.id,
criteria: {

View File

@@ -15,9 +15,9 @@ const validator: Validator = {
/**
* It checks if a user has more then 'minCommits' commits in a specific repo.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, { utils, profile }) {
let allCommits = []

View File

@@ -1,4 +1,4 @@
import { testUtils, validateReputation } from "../.."
import { testUtils, validateCredentials } from "../.."
import twitterFollowers from "./index"
describe("TwitterFollowers", () => {
@@ -11,7 +11,7 @@ describe("TwitterFollowers", () => {
}
})
const result = await validateReputation(
const result = await validateCredentials(
{
id: twitterFollowers.id,
criteria: {
@@ -29,7 +29,7 @@ describe("TwitterFollowers", () => {
it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowers.id,
criteria: {}
@@ -47,7 +47,7 @@ describe("TwitterFollowers", () => {
it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowers.id,
criteria: {
@@ -68,7 +68,7 @@ describe("TwitterFollowers", () => {
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowers.id,
criteria: {

View File

@@ -13,9 +13,9 @@ const validator: Validator = {
/**
* It checks if a user has more then 'minFollowers' followers.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, { utils }) {
const { data } = await utils.api("users/me?user.fields=public_metrics")

View File

@@ -1,4 +1,4 @@
import { testUtils, validateReputation } from "../.."
import { testUtils, validateCredentials } from "../.."
import twitterFollowingUser from "./index"
describe("TwitterFollowingUser", () => {
@@ -14,7 +14,7 @@ describe("TwitterFollowingUser", () => {
]
})
const result = await validateReputation(
const result = await validateCredentials(
{
id: twitterFollowingUser.id,
criteria: {
@@ -32,7 +32,7 @@ describe("TwitterFollowingUser", () => {
it("Should throw an error if a criteria parameter is missing", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowingUser.id,
criteria: {}
@@ -50,7 +50,7 @@ describe("TwitterFollowingUser", () => {
it("Should throw an error if a criteria parameter should not exist", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowingUser.id,
criteria: {
@@ -71,7 +71,7 @@ describe("TwitterFollowingUser", () => {
it("Should throw a type error if a criteria parameter has the wrong type", async () => {
const fun = () =>
validateReputation(
validateCredentials(
{
id: twitterFollowingUser.id,
criteria: {

View File

@@ -13,9 +13,9 @@ const validator: Validator = {
/**
* It checks if a Twitter user follows a specific page.
* @param criteria The reputation criteria used to check user's reputation.
* @param criteria The criteria used to check user's credentials.
* @param context Utility functions and other context variables.
* @returns True if the user meets the reputation criteria.
* @returns True if the user meets the criteria.
*/
async validate(criteria: Criteria, { utils, profile }) {
let allFollowing = []

View File

@@ -805,6 +805,19 @@ __metadata:
languageName: unknown
linkType: soft
"@bandada/credentials@0.12.0, @bandada/credentials@workspace:libs/credentials":
version: 0.0.0-use.local
resolution: "@bandada/credentials@workspace:libs/credentials"
dependencies:
"@bandada/utils": 0.12.0
"@rollup/plugin-typescript": ^11.0.0
rimraf: ^4.1.2
rollup: ^3.17.2
rollup-plugin-cleanup: ^3.2.1
typescript: ^4.9.5
languageName: unknown
linkType: soft
"@bandada/hardhat@workspace:libs/hardhat":
version: 0.0.0-use.local
resolution: "@bandada/hardhat@workspace:libs/hardhat"
@@ -827,19 +840,6 @@ __metadata:
languageName: unknown
linkType: soft
"@bandada/reputation@0.12.0, @bandada/reputation@workspace:libs/reputation":
version: 0.0.0-use.local
resolution: "@bandada/reputation@workspace:libs/reputation"
dependencies:
"@bandada/utils": 0.12.0
"@rollup/plugin-typescript": ^11.0.0
rimraf: ^4.1.2
rollup: ^3.17.2
rollup-plugin-cleanup: ^3.2.1
typescript: ^4.9.5
languageName: unknown
linkType: soft
"@bandada/utils@0.12.0, @bandada/utils@workspace:libs/utils":
version: 0.0.0-use.local
resolution: "@bandada/utils@workspace:libs/utils"
@@ -7610,7 +7610,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "api@workspace:apps/api"
dependencies:
"@bandada/reputation": 0.12.0
"@bandada/credentials": 0.12.0
"@bandada/utils": 0.12.0
"@ethersproject/hash": ^5.7.0
"@nestjs/cli": ^9.0.0
@@ -9864,7 +9864,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "dashboard@workspace:apps/dashboard"
dependencies:
"@bandada/reputation": 0.12.0
"@bandada/credentials": 0.12.0
"@bandada/utils": 0.12.0
"@chakra-ui/react": ^2.5.1
"@chakra-ui/theme-tools": ^2.0.16