@hocuspocus/provider
Advanced tools
Comparing version 1.0.0-alpha.9 to 1.0.0-alpha.10
@@ -6,2 +6,13 @@ # Change Log | ||
# [1.0.0-alpha.10](https://github.com/ueberdosis/hocuspocus/compare/@hocuspocus/provider@1.0.0-alpha.9...@hocuspocus/provider@1.0.0-alpha.10) (2021-08-19) | ||
### Features | ||
* Message Authentication ([#163](https://github.com/ueberdosis/hocuspocus/issues/163)) ([a1e68d5](https://github.com/ueberdosis/hocuspocus/commit/a1e68d5a272742bd17dd92522dfc908277343849)) | ||
# [1.0.0-alpha.9](https://github.com/ueberdosis/hocuspocus/compare/@hocuspocus/provider@1.0.0-alpha.8...@hocuspocus/provider@1.0.0-alpha.9) (2021-08-19) | ||
@@ -8,0 +19,0 @@ |
@@ -1522,21 +1522,24 @@ import * as Y from 'yjs'; | ||
const messagePermissionDenied = 0; | ||
/** | ||
* @callback PermissionDeniedHandler | ||
* @param {any} y | ||
* @param {string} reason | ||
*/ | ||
/** | ||
* | ||
* @param {decoding.Decoder} decoder | ||
* @param {Y.Doc} y | ||
* @param {PermissionDeniedHandler} permissionDeniedHandler | ||
*/ | ||
const readAuthMessage = (decoder, y, permissionDeniedHandler) => { | ||
switch (readVarUint(decoder)) { | ||
case messagePermissionDenied: permissionDeniedHandler(y, readVarString(decoder)); | ||
} | ||
var AuthMessageType; | ||
(function (AuthMessageType) { | ||
AuthMessageType[AuthMessageType["Token"] = 0] = "Token"; | ||
AuthMessageType[AuthMessageType["PermissionDenied"] = 1] = "PermissionDenied"; | ||
AuthMessageType[AuthMessageType["Authenticated"] = 2] = "Authenticated"; | ||
})(AuthMessageType || (AuthMessageType = {})); | ||
const writeAuthentication = (encoder, auth) => { | ||
writeVarUint(encoder, AuthMessageType.Token); | ||
writeVarString(encoder, auth); | ||
}; | ||
const readAuthMessage = (decoder, permissionDeniedHandler, authenticatedHandler) => { | ||
switch (readVarUint(decoder)) { | ||
case AuthMessageType.PermissionDenied: { | ||
permissionDeniedHandler(readVarString(decoder)); | ||
break; | ||
} | ||
case AuthMessageType.Authenticated: { | ||
authenticatedHandler(); | ||
break; | ||
} | ||
} | ||
}; | ||
@@ -1576,9 +1579,4 @@ class MessageReceiver { | ||
} | ||
// TODO: This isn’t really used. Needs to be implemented in the server, or removed here. | ||
applyAuthMessage(provider) { | ||
readAuthMessage(this.message.decoder, provider.document, | ||
// TODO: Add a configureable hook | ||
(provider, reason) => { | ||
console.warn(`Permission denied to access ${provider.url}.\n${reason}`); | ||
}); | ||
readAuthMessage(this.message.decoder, provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider)); | ||
} | ||
@@ -1663,2 +1661,18 @@ applyQueryAwarenessMessage(provider) { | ||
class AuthenticationMessage extends OutgoingMessage { | ||
constructor() { | ||
super(...arguments); | ||
this.type = MessageType.Auth; | ||
this.description = 'Authentication'; | ||
} | ||
get(args) { | ||
if (typeof args.token === 'undefined') { | ||
throw new Error('The authentication message requires token as an argument'); | ||
} | ||
writeVarUint(this.encoder, this.type); | ||
writeAuthentication(this.encoder, args.token); | ||
return this.encoder; | ||
} | ||
} | ||
class AwarenessMessage extends OutgoingMessage { | ||
@@ -1725,2 +1739,3 @@ constructor() { | ||
name: '', | ||
token: null, | ||
parameters: {}, | ||
@@ -1735,2 +1750,4 @@ debug: false, | ||
messageReconnectTimeout: 30000, | ||
onAuthenticated: () => null, | ||
onAuthenticationFailed: () => null, | ||
onOpen: () => null, | ||
@@ -1753,2 +1770,3 @@ onConnect: () => null, | ||
this.isSynced = false; | ||
this.isAuthenticated = false; | ||
this.lastMessageReceived = 0; | ||
@@ -1766,2 +1784,4 @@ this.mux = createMutex(); | ||
this.on('open', this.options.onOpen); | ||
this.on('authenticated', this.options.onAuthenticated); | ||
this.on('authenticationFailed', this.options.onAuthenticationFailed); | ||
this.on('connect', this.options.onConnect); | ||
@@ -1839,2 +1859,13 @@ this.on('message', this.options.onMessage); | ||
} | ||
permissionDeniedHandler(reason) { | ||
this.emit('authenticationFailed', { reason }); | ||
this.log('Permission denied', reason); | ||
this.isAuthenticated = false; | ||
this.shouldConnect = false; | ||
} | ||
authenticatedHandler() { | ||
this.isAuthenticated = true; | ||
this.emit('authenticated'); | ||
this.startSync(); | ||
} | ||
// Ensure that the URL always ends with / | ||
@@ -1862,2 +1893,5 @@ get serverUrl() { | ||
} | ||
get isAuthenticationRequired() { | ||
return !!this.options.token && !this.isAuthenticated; | ||
} | ||
connect() { | ||
@@ -1898,2 +1932,5 @@ this.shouldConnect = true; | ||
this.emit('open', { event }); | ||
if (this.status !== WebSocketStatus.Connected) { | ||
this.webSocketConnectionEstablished(); | ||
} | ||
} | ||
@@ -1905,2 +1942,9 @@ webSocketConnectionEstablished() { | ||
this.emit('connect'); | ||
if (this.isAuthenticationRequired) { | ||
this.send(AuthenticationMessage, { token: this.options.token }); | ||
return; | ||
} | ||
this.startSync(); | ||
} | ||
startSync() { | ||
this.send(SyncStepOneMessage, { document: this.document }); | ||
@@ -1927,5 +1971,2 @@ if (this.awareness.getLocalState() !== null) { | ||
onMessage(event) { | ||
if (this.status !== WebSocketStatus.Connected) { | ||
this.webSocketConnectionEstablished(); | ||
} | ||
this.lastMessageReceived = getUnixTime(); | ||
@@ -1942,2 +1983,3 @@ const message = new IncomingMessage(event.data); | ||
this.emit('close', { event }); | ||
this.isAuthenticated = false; | ||
this.webSocket = null; | ||
@@ -1944,0 +1986,0 @@ if (this.status === WebSocketStatus.Connected) { |
@@ -19,2 +19,3 @@ import * as Y from 'yjs'; | ||
awareness: Awareness; | ||
token: string; | ||
parameters: { | ||
@@ -28,2 +29,6 @@ [key: string]: any; | ||
messageReconnectTimeout: number; | ||
onAuthenticated: () => void; | ||
onAuthenticationFailed: ({ reason: string }: { | ||
reason: any; | ||
}) => void; | ||
onOpen: (event: OpenEvent) => void; | ||
@@ -50,2 +55,3 @@ onConnect: () => void; | ||
isSynced: boolean; | ||
isAuthenticated: boolean; | ||
lastMessageReceived: number; | ||
@@ -63,2 +69,4 @@ mux: mutex.mutex; | ||
awarenessUpdateHandler({ added, updated, removed }: any, origin: any): void; | ||
permissionDeniedHandler(reason: string): void; | ||
authenticatedHandler(): void; | ||
get serverUrl(): string; | ||
@@ -68,2 +76,3 @@ get url(): string; | ||
set synced(state: boolean); | ||
get isAuthenticationRequired(): boolean; | ||
connect(): void; | ||
@@ -74,2 +83,3 @@ disconnect(): void; | ||
webSocketConnectionEstablished(): void; | ||
startSync(): void; | ||
send(Message: OutgoingMessage, args: any, broadcast?: boolean): void; | ||
@@ -76,0 +86,0 @@ onMessage(event: MessageEvent): void; |
import * as encoding from 'lib0/encoding'; | ||
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage'; | ||
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage'; | ||
@@ -11,3 +12,3 @@ import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage'; | ||
message: any; | ||
constructor(Message: Constructable<AwarenessMessage> | Constructable<QueryAwarenessMessage> | Constructable<SyncStepOneMessage> | Constructable<SyncStepTwoMessage> | Constructable<UpdateMessage>, args?: any); | ||
constructor(Message: Constructable<AuthenticationMessage> | Constructable<AwarenessMessage> | Constructable<QueryAwarenessMessage> | Constructable<SyncStepOneMessage> | Constructable<SyncStepTwoMessage> | Constructable<UpdateMessage>, args?: any); | ||
create(): Uint8Array; | ||
@@ -14,0 +15,0 @@ send(webSocket: any): void; |
@@ -15,2 +15,3 @@ import { Awareness } from 'y-protocols/awareness'; | ||
export interface OutgoingMessageArguments { | ||
token: string; | ||
document: Y.Doc; | ||
@@ -17,0 +18,0 @@ awareness: Awareness; |
@@ -21,2 +21,3 @@ /// <reference types="node" /> | ||
configure(configuration: Partial<Configuration>): Hocuspocus; | ||
get authenticationRequired(): boolean; | ||
/** | ||
@@ -23,0 +24,0 @@ * Start the server |
@@ -9,2 +9,4 @@ import { Encoder } from 'lib0/encoding'; | ||
createAwarenessUpdateMessage(awareness: Awareness, changedClients?: Array<any>): OutgoingMessage; | ||
writeAuthenticated(): OutgoingMessage; | ||
writePermissionDenied(reason: string): OutgoingMessage; | ||
writeFirstSyncStepFor(document: Document): OutgoingMessage; | ||
@@ -11,0 +13,0 @@ writeUpdate(update: Uint8Array): OutgoingMessage; |
@@ -8,3 +8,4 @@ /// <reference types="node" /> | ||
Sync = 0, | ||
Awareness = 1 | ||
Awareness = 1, | ||
Auth = 2 | ||
} | ||
@@ -21,2 +22,3 @@ export declare enum WsReadyStates { | ||
export interface Extension { | ||
onAuthenticate?(data: onAuthenticatePayload): Promise<any>; | ||
onChange(data: onChangePayload): Promise<any>; | ||
@@ -37,2 +39,12 @@ onConnect(data: onConnectPayload): Promise<any>; | ||
} | ||
export interface onAuthenticatePayload { | ||
documentName: string; | ||
requestHeaders: IncomingHttpHeaders; | ||
requestParameters: URLSearchParams; | ||
socketId: string; | ||
token: string; | ||
connection: { | ||
readOnly: boolean; | ||
}; | ||
} | ||
export interface onConnectPayload { | ||
@@ -39,0 +51,0 @@ documentName: string; |
{ | ||
"name": "@hocuspocus/provider", | ||
"version": "1.0.0-alpha.9", | ||
"version": "1.0.0-alpha.10", | ||
"description": "hocuspocus provider", | ||
@@ -30,3 +30,3 @@ "homepage": "https://hocuspocus.dev", | ||
}, | ||
"gitHead": "f948b9dfda2d056dff2851670fa6fb4b34081c56", | ||
"gitHead": "13bb2b38c5887bb09c2b6565754888092c8e345b", | ||
"publishConfig": { | ||
@@ -33,0 +33,0 @@ "access": "public" |
@@ -5,3 +5,2 @@ // @ts-nocheck | ||
import * as time from 'lib0/time' | ||
import * as encoding from 'lib0/encoding' | ||
import { Awareness, removeAwarenessStates } from 'y-protocols/awareness' | ||
@@ -20,2 +19,3 @@ import * as mutex from 'lib0/mutex' | ||
import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage' | ||
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage' | ||
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage' | ||
@@ -39,2 +39,3 @@ import { UpdateMessage } from './OutgoingMessages/UpdateMessage' | ||
awareness: Awareness, | ||
token: string, | ||
parameters: { [key: string]: any }, | ||
@@ -46,2 +47,4 @@ WebSocketPolyfill: any, | ||
messageReconnectTimeout: number, | ||
onAuthenticated: () => void, | ||
onAuthenticationFailed: ({ reason: string }) => void, | ||
onOpen: (event: OpenEvent) => void, | ||
@@ -64,2 +67,3 @@ onConnect: () => void, | ||
name: '', | ||
token: null, | ||
parameters: {}, | ||
@@ -74,2 +78,4 @@ debug: false, | ||
messageReconnectTimeout: 30000, | ||
onAuthenticated: () => null, | ||
onAuthenticationFailed: () => null, | ||
onOpen: () => null, | ||
@@ -101,2 +107,4 @@ onConnect: () => null, | ||
isAuthenticated = false | ||
lastMessageReceived = 0 | ||
@@ -122,2 +130,4 @@ | ||
this.on('open', this.options.onOpen) | ||
this.on('authenticated', this.options.onAuthenticated) | ||
this.on('authenticationFailed', this.options.onAuthenticationFailed) | ||
this.on('connect', this.options.onConnect) | ||
@@ -221,2 +231,17 @@ this.on('message', this.options.onMessage) | ||
permissionDeniedHandler(reason: string) { | ||
this.emit('authenticationFailed', { reason }) | ||
this.log('Permission denied', reason) | ||
this.isAuthenticated = false | ||
this.shouldConnect = false | ||
} | ||
authenticatedHandler() { | ||
this.isAuthenticated = true | ||
this.emit('authenticated') | ||
this.startSync() | ||
} | ||
// Ensure that the URL always ends with / | ||
@@ -251,2 +276,6 @@ get serverUrl() { | ||
get isAuthenticationRequired(): boolean { | ||
return !!this.options.token && !this.isAuthenticated | ||
} | ||
connect() { | ||
@@ -296,2 +325,6 @@ this.shouldConnect = true | ||
this.emit('open', { event }) | ||
if (this.status !== WebSocketStatus.Connected) { | ||
this.webSocketConnectionEstablished() | ||
} | ||
} | ||
@@ -305,2 +338,11 @@ | ||
if (this.isAuthenticationRequired) { | ||
this.send(AuthenticationMessage, { token: this.options.token }) | ||
return | ||
} | ||
this.startSync() | ||
} | ||
startSync() { | ||
this.send(SyncStepOneMessage, { document: this.document }) | ||
@@ -332,6 +374,2 @@ | ||
onMessage(event: MessageEvent) { | ||
if (this.status !== WebSocketStatus.Connected) { | ||
this.webSocketConnectionEstablished() | ||
} | ||
this.lastMessageReceived = time.getUnixTime() | ||
@@ -354,2 +392,3 @@ | ||
this.isAuthenticated = false | ||
this.webSocket = null | ||
@@ -356,0 +395,0 @@ |
@@ -5,6 +5,6 @@ import * as decoding from 'lib0/decoding' | ||
import * as syncProtocol from 'y-protocols/sync' | ||
import * as authProtocol from 'y-protocols/auth' | ||
import { MessageType } from './types' | ||
import { HocuspocusProvider } from './HocuspocusProvider' | ||
import { IncomingMessage } from './IncomingMessage' | ||
import { readAuthMessage } from '../../../shared/protocols/auth' | ||
@@ -67,11 +67,7 @@ export class MessageReceiver { | ||
// TODO: This isn’t really used. Needs to be implemented in the server, or removed here. | ||
private applyAuthMessage(provider: HocuspocusProvider) { | ||
authProtocol.readAuthMessage( | ||
readAuthMessage( | ||
this.message.decoder, | ||
provider.document, | ||
// TODO: Add a configureable hook | ||
(provider, reason) => { | ||
console.warn(`Permission denied to access ${provider.url}.\n${reason}`) | ||
}, | ||
provider.permissionDeniedHandler.bind(provider), | ||
provider.authenticatedHandler.bind(provider), | ||
) | ||
@@ -78,0 +74,0 @@ } |
import * as encoding from 'lib0/encoding' | ||
import * as bc from 'lib0/broadcastchannel' | ||
import { OutgoingMessage } from './OutgoingMessage' | ||
import { AuthenticationMessage } from './OutgoingMessages/AuthenticationMessage' | ||
import { AwarenessMessage } from './OutgoingMessages/AwarenessMessage' | ||
@@ -18,2 +18,3 @@ import { QueryAwarenessMessage } from './OutgoingMessages/QueryAwarenessMessage' | ||
constructor(Message: | ||
Constructable<AuthenticationMessage> | | ||
Constructable<AwarenessMessage> | | ||
@@ -20,0 +21,0 @@ Constructable<QueryAwarenessMessage> | |
import * as encoding from 'lib0/encoding' | ||
import { Awareness, encodeAwarenessUpdate } from 'y-protocols/awareness' | ||
import { encodeAwarenessUpdate } from 'y-protocols/awareness' | ||
import { MessageType, OutgoingMessageArguments } from '../types' | ||
@@ -4,0 +4,0 @@ import { OutgoingMessage } from '../OutgoingMessage' |
@@ -1,2 +0,1 @@ | ||
import * as Y from 'yjs' | ||
import * as encoding from 'lib0/encoding' | ||
@@ -3,0 +2,0 @@ import * as syncProtocol from 'y-protocols/sync' |
@@ -14,3 +14,2 @@ import { Awareness } from 'y-protocols/awareness' | ||
encoder: encoding.Encoder | ||
type?: MessageType | ||
@@ -20,2 +19,3 @@ } | ||
export interface OutgoingMessageArguments { | ||
token: string, | ||
document: Y.Doc, | ||
@@ -22,0 +22,0 @@ awareness: Awareness, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
503474
71
5505