Эх сурвалжийг харах

feat(game): automatic access token refresh

Bryan Lee 1 жил өмнө
parent
commit
2ee3533d74

+ 95 - 13
project/authentication/authentication.gd

@@ -43,13 +43,48 @@ var access_token := Option.None()
 ## [/codeblock]
 var refresh_token := Option.None()
 
+const ACCESS_TOKEN_EXPIRY_BUFFER_SEC := 20
+## [codeblock]
+## Option<String>
+## [/codeblock]
+var access_token_expires_at := Option.None() :
+	set(new):
+		access_token_expires_at = new
+		if access_token_expires_at.is_none():
+			return
+		refresh_token_timer_node.stop()
+		var expiry_ts := Time.get_unix_time_from_datetime_string(access_token_expires_at.unwrap())
+		var now_ts = ceili(Time.get_unix_time_from_system())
+		var expires_in_sec = expiry_ts - now_ts - ACCESS_TOKEN_EXPIRY_BUFFER_SEC
+		if expires_in_sec <= 0:
+			refresh_access_token()
+		else:
+			refresh_token_timer_node.wait_time = expires_in_sec
+			refresh_token_timer_node.start()
+
+var refresh_token_timer_node: Timer
+var is_token_refreshing := false
+
 
 func _ready() -> void:
+	setup_providers_node()
+	setup_refresh_token_timer_node()
+
+
+func setup_providers_node() -> void:
 	providers_node = Node.new()
 	providers_node.name = "Providers"
 	add_child(providers_node)
 
 
+func setup_refresh_token_timer_node() -> void:
+	refresh_token_timer_node = Timer.new()
+	refresh_token_timer_node.name = "RefreshTokenTimer"
+	refresh_token_timer_node.one_shot = true
+	add_child(refresh_token_timer_node)
+	refresh_token_timer_node.timeout.connect(refresh_access_token)
+
+
 ## [codeblock]
 ## @returns Result<AuthProvider>
 ## [/codeblock]
@@ -83,24 +118,71 @@ func add_provider(pname: ProviderName) -> Result:
 	if init_result.is_err():
 		providers_node.remove_child(provider)
 		return init_result
-	
+
+	return Result.Ok(provider)
+
+
+## [codeblock]
+## @returns Result<null>
+## [/codeblock]
+func sign_in() -> Result:
 	if user_id.is_some():
-		return Result.Ok(provider)
+		return Result.Ok(null)
 	
-	var sign_in_result := await provider.server_sign_in()
+	if main_provider == null:
+		return Result.Err("main authentication provider not yet initialized")
+	
+	var sign_in_result := await main_provider.server_sign_in()
 	if sign_in_result.is_err():
-		providers_node.remove_child(provider)
 		return sign_in_result
 	
-	var sign_in = sign_in_result.unwrap()
-	match sign_in.type:
+	var sign_in_body = sign_in_result.unwrap()
+	match sign_in_body.type:
 		"success":
-			print("Successfully logged in: ", sign_in.payload)
-			user_id = Option.new(sign_in.payload.user.id)
-			user_name = Option.new(sign_in.payload.user.name)
-			access_token = Option.new(sign_in.payload.access_token)
-			refresh_token = Option.new(sign_in.payload.refresh_token)
+			print("Successfully logged in: ", sign_in_body.payload)
+			user_id = Option.new(sign_in_body.payload.user.id)
+			user_name = Option.new(sign_in_body.payload.user.name)
+			access_token = Option.new(sign_in_body.payload.access_token.value)
+			refresh_token = Option.new(sign_in_body.payload.refresh_token.value)
+
+			access_token_expires_at = Option.new(
+				sign_in_body.payload.access_token.expires_at
+			)
 		"pending_link_or_create":
-			print("Possible existing account: ", sign_in.payload)
+			print("Possible existing account: ", sign_in_body.payload)
+	
+	return Result.Ok(null)
 
-	return Result.Ok(provider)
+
+const AUTH_SERVER_REFRESH_PATH := "/auth/refresh"
+func refresh_access_token() -> Result:
+	if refresh_token.is_none():
+		return Result.Err("cannot refresh access token without refresh token")
+	
+	is_token_refreshing = true
+	var request_result: Result = await HTTPUtils.fetch(
+		Program.AUTH_SERVER_URI + AUTH_SERVER_REFRESH_PATH,
+		["Content-Type: application/json"],
+		HTTPClient.METHOD_POST,
+		JSON.stringify({ "refresh_token": refresh_token.unwrap() })
+	).settled
+
+	if request_result.is_err():
+		push_error(request_result.unwrap_err())
+		is_token_refreshing = false
+		return request_result
+	
+	var response = request_result.unwrap()
+	if response.response_code != HTTPClient.RESPONSE_OK:
+		is_token_refreshing = false
+		return Result.Err("failed to refresh access token: %s" % response.response_code)
+	
+	var body_text: String = response.body.get_string_from_utf8()
+	var body = JSON.parse_string(body_text)
+	access_token = Option.Some(body.access_token.value)
+	refresh_token = Option.Some(body.refresh_token.value)
+
+	access_token_expires_at = Option.new(body.access_token.expires_at)
+
+	is_token_refreshing = false
+	return Result.Ok(null)

+ 7 - 1
project/authentication/providers/auth_provider.gd

@@ -43,6 +43,11 @@ func initialize() -> Result:
 ##   name?: String
 ## }
 ##
+## Token {
+##   value: String
+##   expires_at: String
+## }
+##
 ## UserWithAuthProviders {
 ##   user: User
 ##   providers: Array<AuthProvider>
@@ -51,7 +56,8 @@ func initialize() -> Result:
 ## SignInSuccess {
 ##   type: "success"
 ##   payload: {
-##     access_token: String
+##     access_token: Token
+##     refresh_token: Token
 ##     user: UserWithAuthProviders
 ##   }
 ## }

+ 6 - 3
project/main/main.gd

@@ -11,10 +11,13 @@ func _ready() -> void:
 		await load_game_screen()
 		return
 	
-	var provider_result := await Authentication.initialize_main_provider()
-	if provider_result.is_err():
-		print(provider_result.unwrap_err())
+	var init_result := await Authentication.initialize_main_provider()
+	if init_result.is_err():
+		print(init_result.unwrap_err())
 		return
+	var sign_in_result := await Authentication.sign_in()
+	if sign_in_result.is_err():
+		print(sign_in_result.unwrap_err())
 
 	await load_debug_auth_screen()