Description
I have an API which I have created and I have some endpoints protected. The problem I am facing now on the client making a request is that the first request comes through with the Authorization header provided but a second request is blocked because Authorization is not present.
I can confirm that Authorization is present and worked perfectly when I was running Typescript till I recreated the endpoints in Go with Gin.
How to reproduce
Call estimate endpoint from client (iOS app) response succeceds
Make a second call from Client (iOS app) response failed because it is not taking the Authorization header which contains token
package main
import (
"github.com/gin-gonic/gin"
)
type App struct {
Router *gin.Engine
Gateman *gateman.Gateman
Database *mongo.Client
}
func (a *App) StartApp() {
err := godotenv.Load()
if err != nil {
fmt.Printf("Could not load .env \n")
}
a.Database = database.DB
a.Router = gin.New()
a.Gateman = middleware.Gateman()
a.Router.Use(gin.Recovery())
a.Router.Use(middleware.DefaultHelmet())
a.Router.Use(middleware.GinContextToContextMiddleware())
a.Router.Use(middleware.RequestID(nil))
a.Router.Use(middleware.ErrorHandler())
a.Router.Use(middleware.Logger("package-service"))
connection, err := amqp091.Dial(os.Getenv("AMQP_URL"))
if err != nil {
log.Fatal(fmt.Printf("Error on dial %v\n", err.Error()))
}
routes.Routes(a.Router, database.GetDatabase(a.Database), a.Gateman, connection)
}
func (a *App) Run(addr string) {
logs := log.New(os.Stdout, "package-service", log.LstdFlags)
server := &http.Server{
Addr: addr,
Handler: a.Router,
ErrorLog: logs,
IdleTimeout: 120 * time.Second, // max time for connections using TCP Keep-Alive
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
go func() {
if err := server.ListenAndServe(); err != nil {
logs.Fatal(err)
}
}()
// trap sigterm or interrupt and gracefully shutdown the server
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
signal.Notify(c, os.Kill)
sig := <-c
logs.Println("Recieved terminate, graceful shutdown", sig)
tc, _ := context.WithTimeout(context.Background(), 30*time.Second)
server.Shutdown(tc)
}
func Routes(r *gin.Engine, db *mongo.Database, g *gateman.Gateman, conn *amqp091.Connection) {
atHandler := pc.NewPackagesController(ps.NewPackagesService(pr.NewPackagesRepository(db)), g, events.NewEventEmitter(conn))
r.Use(CORS())
v1 := r.Group("/api/v1/package")
{
v1.POST("/query", GraphqlHandler(db, directives.NewDirectivesManager(g)))
v1.GET("/", PlaygroundHandler(db))
v1.POST("/", g.Guard([]string{"user"}, nil), atHandler.Create)
v1.POST("/estimate", g.Guard([]string{"user"}, nil), atHandler.Estimate)
v1.PUT("/:packageID", g.Guard([]string{"user", "admin"}, nil), atHandler.Update)
v1.PUT("/:packageID/assign", g.Guard([]string{"admin"}, nil), atHandler.Assign)
v1.POST("/:packageID/cancel", g.Guard([]string{"user", "admin"}, nil), atHandler.CancelRequest)
v1.POST("/:packageID/complete", g.Guard([]string{"admin"}, nil), atHandler.Complete)
v1.POST("/:packageID/reject", g.Guard([]string{"admin"}, nil), atHandler.RejectRequest)
v1.GET("/healthz", atHandler.GetHealth)
}
r.GET("/", atHandler.GetUP)
}
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func main() {
start := App{}
start.StartApp()
start.Run(":3009")
}
Expectations
All endpoints that are Guarded simply checks the header for Authorization and if provided in the request, it should be successful
Actual result
First request succeed /estimate
Second request / POST request fails to accept Authorization header
Also irrespective of what the first post request is, the second post request just never accept the Authorization header
Also need to mention that I do not have this issue with postman. Both request run independently but using another client for the request, gives this problem
Environment
go version: 1.19
gin version (or commit ref): v1.8.1
operating system: Mac and iOS mobile
Here is my client code
func request<T>(with builder: BaseRequest) -> AnyPublisher<T, APIError> where T: Codable {
request(with: builder, customDecoder: JSONDecoder())
}
func request<T>(with builder: BaseRequest, customDecoder: JSONDecoder) -> AnyPublisher<T, APIError> where T: Codable {
let encoding: ParametersEncoder = [.get, .delete].contains(builder.method) ? URLParameretersEncoder() : JSONParametersEncoder()
customDecoder.keyDecodingStrategy = .convertFromSnakeCase
var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = builder.baseUrl
components.path = "/api/v1" + builder.path
guard let url = components.url else {
preconditionFailure("Invalid URL components: \(components)")
}
return url
}
var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 46.0)
urlRequest.httpMethod = builder.method.rawValue
builder.headers.forEach { key, value in
urlRequest.setValue(value, forHTTPHeaderField: key)
}
if let token = tokenManager.token {
urlRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
// urlRequest.setValue("ABC", forHTTPHeaderField: "testing123")
}
if let parameters = builder.parameters {
guard let encoded = try? encoding.encode(parameters: parameters, in: urlRequest) else {
fatalError()
}
urlRequest = encoded
}
self.log(request: urlRequest)
return URLSession.shared
.dataTaskPublisher(for: urlRequest)
.receive(on: DispatchQueue.main)
.mapError {_ -> APIError in
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return .unknown
}
.flatMap { data, response -> AnyPublisher<T, APIError> in
guard let response = response as? HTTPURLResponse else {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return Fail(error: APIError.invalidResponse).eraseToAnyPublisher()
}
self.log(response: response, data: data, error: nil)
if (200...299).contains(response.statusCode) {
return Just(data)
.decode(type: T.self, decoder: customDecoder)
// .map {
// print($0)
// return $0
// } //added
.mapError {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return .decodingError(underlyingError: $0)
}
.eraseToAnyPublisher()
} else {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
if response.statusCode == 401 {
// Send notification to remove corrdinator and return to root
// rxNotificationCenter.post(.invalidToken)
}
guard let errorResponse = try? customDecoder.decode(BaseResponse.self, from: data) else {
return Fail(error: APIError.decodingError(underlyingError: NSError("Can't decode error"))).eraseToAnyPublisher()
}
return Fail(error: APIError.server(response: errorResponse))
.eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
protocol BaseRequest {
var baseUrl: String { get }
var path: String { get }
var headers: HTTPHeaders { get }
var method: HTTPRequestMethod { get }
var parameters: HTTPParameters { get }
}
public typealias HTTPHeaders = [String: String]
public typealias HTTPParameters = [String: Any]?
Another point, calling a single endpoint multiple time, works fine, calling a different one is where the header is rejected
I want to let the people login to the certain domain, but it always shows a error.
Cannot convert value of type 'String' to expected argument type 'Int').
how can I deal with it?
import UIKit
import FirebaseAuth
class ViewController: UIViewController {
#IBOutlet var emailTextField: UITextField!
#IBOutlet var passwordTextField: UITextField!
#IBAction func signUp(_ sender: Any) {
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) { (result, error) in
if error != nil{
print(error)
}else{
self.performSegue(withIdentifier: "createdUser", sender: self)
}
}
}
#IBAction func signIn(_ sender: Any) {
Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!) { (result, error) in
if error != nil{
print(error)
}else{
var hi = self.emailTextField.text
if hi?.suffix("#hkugac.edu.hk"){ //error here
self.performSegue(withIdentifier: "logged", sender: self)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Try using the hasSuffix method. https://developer.apple.com/documentation/swift/string/1541149-hassuffix
var hi = self.emailTextField.text
if let mail = hi, mail.haSuffix("#hkugac.edu.hk")
{
self.performSegue(withIdentifier: "logged", sender: self)
}
else
{
// Dome some error handling here.
}
Also, I think it would be better to check the mail before you call the signIn function.
i just made one wrapper class for share helper my code is as follow.
let activityVC = UIActivityViewController(activityItems: [message], applicationActivities: nil)
activityVC.setValue(subject, forKey: "subject")
activityVC.completionWithItemsHandler = {(activityType: String!, completed:Bool, objects:[AnyObject]!, error:NSError!) in
}
fromVC.presentViewController(activityVC, animated: true, completion: nil)
problem starts here, UIActivityItemSource methods not being call
override func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
switch activityType
{
case UIActivityTypeMail:
return msg
case UIActivityTypeMessage:
return msg
case UIActivityTypePostToFacebook:
return msg
case UIActivityTypePostToTwitter:
return strTwitterShare
default:
return msg
}
}
thanks for help
It should work if you use let activityVC = UIActivityViewController(activityItems: [self], applicationActivities: nil)
... then your message is provided inside your itemForActivityType method.
The line below, at SQLHandler.translateQuery("Do mysql stuff"), is throwing the error: '(String) -> String' is not convertible to 'SQLHandler'. Why is it doing this? Thank you in advance.
Code 1 (Used wherever and whenever needed)
var query: String = "mysql stuff"
SQLHandler.sendQuery(SQLHandler.translateQuery("domain and \(query)"))
Code 2, SQLHandler.swift (Called whenever needed)
import Foundation
class SQLHandler {
func translateQuery(queryToTranslate: String) -> String{
println(queryToTranslate)
return queryToTranslate.stringByReplacingOccurrencesOfString(" ", withString: "_", options: NSStringCompareOptions.LiteralSearch, range: nil)
}
func sendQuery(query: String){
println(query)
let url = NSURL(string: "url and query goes here")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in })
task.resume()
}
}
You are calling an instance method on the class itself.
Either create an instance and call the method:
var sqlHandler = SQLHandler()
sqlHandler.translateQuery("domain and \(query)")
or define the methods as class methods :
class func translateQuery(queryToTranslate: String) -> String ...
class func sendQuery(query: String) ...
I have the following code linked up to a button that should play some audio.
var ExamplePlayer = AVAudioPlayer()
var Example = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Example", ofType: "mp3")!)
func ExampleAudio(){
ExamplePlayer = AVAudioPlayer(contentsOfURL: Example, error: nil)
ExamplePlayer.prepareToPlay()
ExamplePlayer.play()
}
#IBAction func anotherExampleAudio(sender: AnyObject) {
ExampleAudio()
}
This is the error I'm getting.
fatal error: unexpectedly found nil while unwrapping an Optional value
What's going on?