Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(2627): Generating V2 invite links #2665

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Adds basic sigchain functions ([#2625](https://github.com/TryQuiet/quiet/issues/2625))
* Instantiates signature chain when creating communities and reloading application ([#2626](https://github.com/TryQuiet/quiet/issues/2626))
* Added in LFA-ready invite links ([#2627](https://github.com/TryQuiet/quiet/issues/2627))
* Generating LFA-ready invite links when a sigchain is configured ([#2627](https://github.com/TryQuiet/quiet/issues/2627))

### Fixes
* Changed company name in app to "A Quiet LLC" ([#2642] (https://github.com/TryQuiet/quiet/issues/2642))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const logger = createLogger('auth:inviteService')

export const DEFAULT_MAX_USES = 1
export const DEFAULT_INVITATION_VALID_FOR_MS = 604_800_000 // 1 week
export const DEFAULT_LONG_LIVED_MAX_USES = 0 // no limit
export const DEFAULT_LONG_LIVED_VALID_FOR_MS = 0 // no limit

class InviteService extends ChainServiceBase {
public static init(sigChain: SigChain): InviteService {
Expand All @@ -32,7 +34,11 @@ class InviteService extends ChainServiceBase {
maxUses: number = DEFAULT_MAX_USES,
seed?: string
): InviteResult {
const expiration = (Date.now() + validForMs) as UnixTimestamp
let expiration: UnixTimestamp = 0 as UnixTimestamp
if (validForMs > 0) {
expiration = (Date.now() + validForMs) as UnixTimestamp
}

const invitation: InviteResult = this.sigChain.team.inviteMember({
seed,
expiration,
Expand All @@ -41,6 +47,10 @@ class InviteService extends ChainServiceBase {
return invitation
}

public createLongLivedUserInvite(): InviteResult {
return this.createUserInvite(DEFAULT_LONG_LIVED_VALID_FOR_MS, DEFAULT_LONG_LIVED_MAX_USES)
}

public createDeviceInvite(validForMs: number = DEFAULT_INVITATION_VALID_FOR_MS, seed?: string): InviteResult {
const expiration = (Date.now() + validForMs) as UnixTimestamp
const invitation: InviteResult = this.sigChain.team.inviteDevice({
Expand All @@ -50,6 +60,23 @@ class InviteService extends ChainServiceBase {
return invitation
}

public isValidLongLivedUserInvite(id: Base58): boolean {
logger.info(`Validating LFA invite with ID ${id}`)
const invites = this.getAllInvites()
for (const invite of invites) {
if (
invite.id === id && // is correct invite
!invite.revoked && // is not revoked
invite.maxUses == 0 && // is an unlimited invite
invite.expiration == 0 // is an unlimited invite
) {
return true
}
}

return false
}

public revoke(id: string) {
this.sigChain.team.revokeInvitation(id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getLibp2pAddressesFromCsrs, removeFilesFromDir } from '../common/utils'

import { LazyModuleLoader } from '@nestjs/core'
import { createLibp2pAddress, filterValidAddresses, isPSKcodeValid } from '@quiet/common'
import { CertFieldsTypes, createRootCA, getCertFieldValue, loadCertificate } from '@quiet/identity'
import { CertFieldsTypes, getCertFieldValue, loadCertificate } from '@quiet/identity'
import {
ChannelMessageIdsResponse,
ChannelSubscribedPayload,
Expand All @@ -28,8 +28,6 @@ import {
FileMetadata,
GetMessagesPayload,
InitCommunityPayload,
InvitationDataV2,
InvitationDataVersion,
MessagesLoadedPayload,
NetworkDataPayload,
NetworkInfo,
Expand Down Expand Up @@ -62,7 +60,6 @@ import { SocketService } from '../socket/socket.service'
import { StorageService } from '../storage/storage.service'
import { StorageEvents } from '../storage/storage.types'
import { StorageServiceClient } from '../storageServiceClient/storageServiceClient.service'
import { ServerStoredCommunityMetadata } from '../storageServiceClient/storageServiceClient.types'
import { Tor } from '../tor/tor.service'
import { ConfigOptions, GetPorts, ServerIoProviderTypes } from '../types'
import { ServiceState, TorInitState } from './connections-manager.types'
Expand All @@ -71,6 +68,7 @@ import { createLogger } from '../common/logger'
import { createUserCsr, getPubKey, loadPrivateKey, pubKeyFromCsr } from '@quiet/identity'
import { config } from '@quiet/state-manager'
import { SigChainService } from '../auth/sigchain.service'
import { Base58, InviteResult } from '3rd-party/auth/packages/auth/dist'

@Injectable()
export class ConnectionsManagerService extends EventEmitter implements OnModuleInit {
Expand Down Expand Up @@ -589,7 +587,12 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
this.logger.error('Community name is required to create sigchain')
return community
}
this.sigChainService.createChain(community.name, identity.nickname, true)

this.logger.info(`Creating new LFA chain`)
await this.sigChainService.createChain(community.name, identity.nickname, true)
// this is the forever invite that all users get
this.logger.info(`Creating long lived LFA invite code`)
this.socketService.emit(SocketActionTypes.CREATE_LONG_LIVED_LFA_INVITE)
return community
}

Expand Down Expand Up @@ -908,13 +911,56 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI
callback(await this.leaveCommunity())
})

// Local First Auth

this.socketService.on(
SocketActionTypes.CREATE_LONG_LIVED_LFA_INVITE,
async (callback?: (response: InviteResult | undefined) => void) => {
this.logger.info(`socketService - ${SocketActionTypes.CREATE_LONG_LIVED_LFA_INVITE}`)
if (this.sigChainService.activeChainTeamName != null) {
const invite = this.sigChainService.getActiveChain().invites.createLongLivedUserInvite()
this.serverIoProvider.io.emit(SocketActionTypes.CREATED_LONG_LIVED_LFA_INVITE, invite)
if (callback) callback(invite)
} else {
this.logger.warn(`No sigchain configured, skipping long lived LFA invite code generation!`)
if (callback) callback(undefined)
}
}
)

this.socketService.on(
SocketActionTypes.VALIDATE_OR_CREATE_LONG_LIVED_LFA_INVITE,
async (
inviteId: Base58,
callback: (response: { isValid: boolean; newInvite?: InviteResult } | undefined) => void
) => {
this.logger.info(`socketService - ${SocketActionTypes.VALIDATE_OR_CREATE_LONG_LIVED_LFA_INVITE}`)
if (this.sigChainService.activeChainTeamName != null) {
if (this.sigChainService.getActiveChain().invites.isValidLongLivedUserInvite(inviteId)) {
this.logger.info(`Invite is a valid long lived LFA invite code!`)
callback({ isValid: true })
} else {
this.logger.info(`Invite is an invalid long lived LFA invite code! Generating a new code!`)
const newInvite = this.sigChainService.getActiveChain().invites.createLongLivedUserInvite()
this.serverIoProvider.io.emit(SocketActionTypes.CREATED_LONG_LIVED_LFA_INVITE, newInvite)
callback({ isValid: false, newInvite })
}
} else {
this.logger.warn(`No sigchain configured, skipping long lived LFA invite code validation/generation!`)
callback(undefined)
}
}
)

// Username registration

this.socketService.on(SocketActionTypes.ADD_CSR, async (payload: SaveCSRPayload) => {
this.logger.info(`socketService - ${SocketActionTypes.ADD_CSR}`)
await this.storageService?.saveCSR(payload)
})

// Public Channels

this.socketService.on(
SocketActionTypes.CREATE_CHANNEL,
async (args: CreateChannelPayload, callback: (response?: CreateChannelResponse) => void) => {
Expand Down
24 changes: 24 additions & 0 deletions packages/backend/src/nest/socket/socket.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ConfigOptions, ServerIoProviderTypes } from '../types'
import { suspendableSocketEvents } from './suspendable.events'
import { createLogger } from '../common/logger'
import type net from 'node:net'
import { Base58, InviteResult } from '@localfirst/auth'

@Injectable()
export class SocketService extends EventEmitter implements OnModuleInit {
Expand Down Expand Up @@ -205,6 +206,29 @@ export class SocketService extends EventEmitter implements OnModuleInit {
this.emit(SocketActionTypes.SET_USER_PROFILE, profile)
})

// ====== Local First Auth ======

socket.on(
SocketActionTypes.CREATE_LONG_LIVED_LFA_INVITE,
async (callback: (response: InviteResult | undefined) => void) => {
this.logger.info(`Creating long lived LFA invite code`)
this.emit(SocketActionTypes.CREATE_LONG_LIVED_LFA_INVITE, callback)
}
)

socket.on(
SocketActionTypes.VALIDATE_OR_CREATE_LONG_LIVED_LFA_INVITE,
async (inviteId: Base58, callback: (response: InviteResult | undefined) => void) => {
this.logger.info(`Validating long lived LFA invite with ID ${inviteId} or creating a new one`)
this.emit(SocketActionTypes.VALIDATE_OR_CREATE_LONG_LIVED_LFA_INVITE, inviteId, callback)
}
)

socket.on(SocketActionTypes.CREATED_LONG_LIVED_LFA_INVITE, (invite: InviteResult) => {
this.logger.info(`Created new long lived LFA invite code with id ${invite.id}`)
this.emit(SocketActionTypes.CREATED_LONG_LIVED_LFA_INVITE, invite)
})

// ====== Misc ======

socket.on(SocketActionTypes.LOAD_MIGRATION_DATA, async (data: Record<string, any>) => {
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/invitationLink/invitationLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ const composeInvitationUrl = (baseUrl: string, data: InvitationDataV1 | Invitati
url.searchParams.append(PSK_PARAM_KEY, data.psk)
url.searchParams.append(OWNER_ORBIT_DB_IDENTITY_PARAM_KEY, data.ownerOrbitDbIdentity)
url.searchParams.append(AUTH_DATA_KEY, encodeAuthData(data.authData))
break
}
return url.href
}
Expand Down
Loading
Loading