Skip to content

Commit 85f46da

Browse files
authored
Allow authToken to be a function (#6751)
Similar to params. When reconnecting, we call the function to get a potentially updated authToken. Previously, to update the auth token, a user would have to mutate socket.authToken, which is not publicly documented.
1 parent a306964 commit 85f46da

4 files changed

Lines changed: 54 additions & 12 deletions

File tree

assets/js/phoenix/socket.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ import Timer from "./timer"
8787
* Defaults to 20s (double the server long poll timer).
8888
*
8989
* @param {(Object|function)} [opts.params] - The optional params to pass when connecting
90-
* @param {string} [opts.authToken] - the optional authentication token to be exposed on the server
91-
* under the `:auth_token` connect_info key.
90+
* @param {(string|function)} [opts.authToken] - the optional authentication token to be exposed on the server
91+
* under the `:auth_token` connect_info key. Can be a string or a function that returns a string.
9292
* @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.
9393
*
9494
* Defaults to "arraybuffer"
@@ -201,7 +201,7 @@ export default class Socket {
201201
}
202202
this.teardown(() => this.connect())
203203
}, this.reconnectAfterMs)
204-
this.authToken = opts.authToken
204+
this.authToken = opts.authToken && closure(opts.authToken)
205205
}
206206

207207
/**
@@ -396,7 +396,7 @@ export default class Socket {
396396
// Sec-WebSocket-Protocol based token
397397
// (longpoll uses Authorization header instead)
398398
if(this.authToken){
399-
protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`]
399+
protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken()).replace(/=/g, "")}`]
400400
}
401401
this.conn = new this.transport(this.endPointURL(), protocols)
402402
this.conn.binaryType = this.binaryType

assets/test/channel_test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,19 @@ describe("with transport", function (){
6565
it("sets subprotocols when authToken is provided", function (){
6666
const authToken = "1234"
6767
const socket = new Socket("/socket", {authToken})
68-
68+
6969
socket.connect()
7070
expect(socket.conn.protocols).toEqual(["phoenix", "base64url.bearer.phx.MTIzNA"])
7171
})
72+
73+
it("sets subprotocols when authToken is a function", function (){
74+
let authToken = "1234"
75+
const socket = new Socket("/socket", {authToken: () => authToken})
76+
77+
authToken = "5678"
78+
socket.connect()
79+
expect(socket.conn.protocols).toEqual(["phoenix", "base64url.bearer.phx.NTY3OA"])
80+
})
7281
})
7382

7483
describe("updating join params", function (){

assets/test/longpoll_test.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,9 @@ describe("Socket with LongPoll", () => {
282282
const authToken = "my-auth-token"
283283
const socket = new Socket("/socket", {
284284
transport: LongPoll,
285-
params: {token: authToken}
285+
authToken
286286
})
287-
288-
// Set auth token
289-
socket.authToken = authToken
290-
287+
291288
// Mock the transport to capture the protocols argument
292289
socket.transport = jest.fn(() => ({
293290
onopen: jest.fn(),
@@ -369,4 +366,3 @@ describe("Ajax.request", () => {
369366
)
370367
})
371368
})
372-

assets/test/socket_test.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {jest} from "@jest/globals"
22
import {WebSocket, Server as WebSocketServer} from "mock-socket"
33
import {encode} from "./serializer"
44
import {Socket, LongPoll} from "../js/phoenix"
5-
import {SOCKET_STATES} from "../js/phoenix/constants"
5+
import {AUTH_TOKEN_PREFIX, SOCKET_STATES} from "../js/phoenix/constants"
66

77
let socket
88

@@ -222,6 +222,43 @@ describe("with transports", function (){
222222
socket.connect()
223223
expect(conn).toBe(socket.conn)
224224
})
225+
226+
it("uses updated authToken function value when reconnecting", function (){
227+
jest.useFakeTimers()
228+
229+
try {
230+
let authToken = "old-token"
231+
const connections = []
232+
class ReconnectingWebSocket {
233+
constructor(_url, protocols){
234+
this.protocols = protocols
235+
this.readyState = SOCKET_STATES.open
236+
this.bufferedAmount = 0
237+
connections.push(this)
238+
}
239+
close(){ this.readyState = SOCKET_STATES.closed }
240+
send(){}
241+
}
242+
243+
socket = new Socket("/socket", {
244+
transport: ReconnectingWebSocket,
245+
authToken: () => authToken,
246+
reconnectAfterMs: () => 10
247+
})
248+
249+
socket.connect()
250+
authToken = "new-token"
251+
socket.onConnClose({code: 1006})
252+
jest.advanceTimersByTime(10)
253+
254+
expect(connections.length).toBe(2)
255+
expect(connections[0].protocols).toEqual(["phoenix", `${AUTH_TOKEN_PREFIX}${btoa("old-token").replace(/=/g, "")}`])
256+
expect(connections[1].protocols).toEqual(["phoenix", `${AUTH_TOKEN_PREFIX}${btoa("new-token").replace(/=/g, "")}`])
257+
expect(socket.conn).toBe(connections[1])
258+
} finally {
259+
jest.useRealTimers()
260+
}
261+
})
225262
})
226263

227264
describe("connect with long poll", function (){

0 commit comments

Comments
 (0)