javascriptで時刻表示のタイムゾーンがおかしくなる場合の確認事項(Moment.js使用)
npm install --save moment-timezoneをしてある前提です。
Timezoneを明示的に指定するには
import * as moment from 'moment-timezone'; moment.tz.setDefault('Asia/Tokyo');
のようにしますが、ここで
datetime = moment().format('YYYY-MM-DDTHH:MM');
のようにして使うと日付入力部品で9時間”先に”進んでしまうことがあります。 これはdatetime内に文字列として保存された日時がどこのタイムゾーンなのかが把握されてしまうために 発生する問題で、明示的に
datetime = moment().format('YYYY-MM-DDTHH:MM+9:00');
としてやることでブラウザもタイムゾーンを理解してくれて正確な時間が表示されるようになります。
Ionicでルーティング先のURLを直接叩くと404になる問題の解決方法
Ionic v4向けの記事です。
IonicでTab型のアプリを作る場合 ionic serve でプレビューしているときは
http://localhost:8100/tabs/tab1
と直接URL指定してもTab1の内容が表示されるのですが、 ionic buildしてwwwディレクトリをデプロイした場合は404になってしまいます。 また、本来やってはだめですがbuttonなどで飛び先をhref="/tabs/tab2"と指定した場合も404になってしまいます。 ここはちゃんと
<ion-button routerLink="/faq" routerDirection="root">FAQ</ion-button>
のようにrouteLinkを使いましょう。 ここまでやっても/tabs/tab1に直接飛ぶだけでなく、正常に開いた状態でページをリロードしても404になります。
なぜか。
Ionicはv4からルーティングがAngularに寄ってきたのですが、まだ特殊なのが必ず/index.htmlを開く前提になっているため、/tabs/tab1/index.htmlを開こうとしてしまうのです。そこで、.htaccessに下記のようにRewriteを指定してやるとうまくいきます。
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.html [L] </IfModule>
Goa(v2)でjwt認証
GoaでJWT認証できるAPIを作ろうとこちらのリンクを参考にしたのですがそれでもハマりポイントがあったのでメモ
goaで作ったAPIサーバにJWT認証を追加する | Fusic Tech Blog
jwtキーを発行するURIにBasic認証をかけ、認証が成功するとキーを発行するようにしています。
design.go
package design import ( . "github.com/goadesign/goa/design" . "github.com/goadesign/goa/design/apidsl" ) var _ = API("Secure", func() { Title("Secure API") Description("Secure API use JWT") BasePath("/api") Scheme("http") Host("localhost:8080") }) var BasicAuth = BasicAuthSecurity("BasicAuth", func() { Description("Use client ID and client secret to authenticate") }) var JWT = JWTSecurity("jwt", func() { Header("Authorization") Scope("api:access", "API access") }) var _ = Resource("jwt", func() { // Resources group related API endpoints DefaultMedia(SuccessMedia) // services. Security(JWT, func() { Scope("api:access") }) Action("signin", func() { // Actions define a single API endpoint together // ←ここでBasic認証。成功するとJWTトークンが取れる Description("Get JWT Token") // with its path, parameters (both path Security(BasicAuth) Routing(GET("/jwt/signin")) // parameters and querystring values) and payload Response(NoContent, func() { Headers(func() { Header("Authorizatiton", String, "Generated JWT") }) }) Response(Unauthorized) // of HTTP responses. }) Action("secure", func() { // ←取得したJWTが必要なAPI Routing(GET("/jwt")) Response(OK) Response(Unauthorized) }) }) var SuccessMedia = MediaType("application/vnd.goa.jwt.test.success", func() { Description("A station of mine") Attributes(func() { // Attributes define the media type shape. Attribute("ok", Boolean, "Always true") Required("ok") }) View("default", func() { // View defines a rendering of the media type. Attribute("ok") // Media types may have multiple views and must }) })
コード生成
goagen bootstrap -d github.com/hryktrd/jwtTest/design
実装
- main.goにmiddleware追加
//go:generate goagen bootstrap -d github.com/hryktrd/jwtTest/design package main import ( "context" "fmt" "io/ioutil" "net/http" "os" jwtgo "github.com/dgrijalva/jwt-go" "github.com/goadesign/goa" "github.com/goadesign/goa/middleware" "github.com/goadesign/goa/middleware/security/jwt" "github.com/hryktrd/jwtTest/app" ) func main() { // Create service service := goa.New("Secure") // Mount middleware service.Use(middleware.RequestID()) service.Use(middleware.LogRequest(true)) service.Use(middleware.ErrorHandler(service, true)) service.Use(middleware.Recover()) // ここから追加 // JWTキー用追加コード pem, err := ioutil.ReadFile("./jwtkey/jwt.key.pub.pkcs8") if err != nil { fmt.Printf("%s", err) os.Exit(1) } key, err := jwtgo.ParseRSAPublicKeyFromPEM([]byte(pem)) if err != nil { fmt.Printf("%s", err) os.Exit(1) } jwtHandler := func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { return h(ctx, rw, req) } } app.UseJWTMiddleware(service, jwt.New(jwt.NewSimpleResolver([]jwt.Key{key}), jwtHandler, app.NewJWTSecurity())) basicAuthHandler := func(h goa.Handler) goa.Handler { return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { user, pass, ok := req.BasicAuth() if !ok || user != "foo" || pass != "bar" { errUnauthorized := goa.NewErrorClass("unauthorized", 401) return errUnauthorized("missing auth") } return h(ctx, rw, req) } } app.UseBasicAuthMiddleware(service, basicAuthHandler) // 追加ここまで // Mount "jwt" controller c := NewJWTController(service) app.MountJWTController(service, c) // Start service if err := service.ListenAndServe(":8080"); err != nil { service.LogError("startup", "err", err) } }
- JWTコントローラ実装 jwt.goにサインイン後JWTを返すところとJWTを読み込む機能を実装
package main import ( "fmt" "io/ioutil" "time" "github.com/hryktrd/jwtTest/app" jwtgo "github.com/dgrijalva/jwt-go" "github.com/goadesign/goa" "github.com/goadesign/goa/middleware/security/jwt" "github.com/gofrs/uuid" ) // JWTController implements the jwt resource. type JWTController struct { *goa.Controller } // NewJWTController creates a jwt controller. func NewJWTController(service *goa.Service) *JWTController { return &JWTController{Controller: service.NewController("JWTController")} } // JWTを読み込む実装 // Secure runs the secure action. func (c *JWTController) Secure(ctx *app.SecureJWTContext) error { // JWTController_Secure: start_implement // Retrieve the token claims token := jwt.ContextJWT(ctx) if token == nil { return fmt.Errorf("JWT token is missing from context") // internal error } claims := token.Claims.(jwtgo.MapClaims) // Use the claims to authorize subject := claims["sub"] if subject != "subject" { // A real app would probably use an "Unauthorized" response here res := &app.GoaJWTTestSuccess{OK: false} return ctx.OK(res) } res := &app.GoaJWTTestSuccess{OK: true} return ctx.OK(res) // JWTController_Secure: end_implement } // サインインしてJWTを返す実装 // Signin runs the signin action. func (c *JWTController) Signin(ctx *app.SigninJWTContext) error { // JWTController_Signin: start_implement b, err := ioutil.ReadFile("./jwtkey/jwt.key") if err != nil { return fmt.Errorf("read private key file: %s", err) // internal error } privKey, err := jwtgo.ParseRSAPrivateKeyFromPEM(b) if err != nil { return fmt.Errorf("failed to parse RSA private key: %s", err) // internal error } token := jwtgo.New(jwtgo.SigningMethodRS512) in3m := time.Now().Add(time.Duration(3) * time.Minute).Unix() token.Claims = jwtgo.MapClaims{ "iss": "Issuer", // who creates the token and signs it "aud": "Audience", // to whom the token is intended to be sent "exp": in3m, // time when the token will expire (10 minutes from now) "jti": uuid.Must(uuid.NewV4()).String(), // a unique identifier for the token "iat": time.Now().Unix(), // when the token was issued/created (now) "nbf": 2, // time before which the token is not yet valid (2 minutes ago) "sub": "subject", // the subject/principal is whom the token is about "scopes": "api:access", // token scope - not a standard claim } signedToken, err := token.SignedString(privKey) if err != nil { return fmt.Errorf("failed to sign token: %s", err) // internal error } ctx.ResponseData.Header().Set("Authorization", "Bearer "+signedToken) return ctx.NoContent() return nil // JWTController_Signin: end_implement }
JWT生成用鍵準備
$ mkdir jwtkey $ ssh-keygen -t rsa -b 4096 -f jwtkey/jwt.key
公開鍵はPKCS8化しないといけないので変換
$ ssh-keygen -f .\jwtkey\jwt.key -e -m pkcs8 > .\jwtkey\jwt.key.pkcs8
↑このファイルがWindowsでやるとUTF16 LEになってしまったのでVSCodeでUTF8にして保存しなおしました。
実行
$ go run .\main.go .\jwt.go
localhost:8080/jwt/siginin にfoo/bar のBasic認証でアクセスするとBearerトークンが返され、そのトークンで localhost:8080/jwt にアクセスすると200が返ってきます。
Chrome (>=75)でimg,iframeの遅延ロードができるようになった
画像の遅延ローディングと言えば今まで大量の画像をスクロールして見る時、 表示速度を落とさないためにはJavascriptなどを使って今ブラウザ画面内に 入る範囲の画像だけをロードするなど苦労された方も多いのではないでしょうか?
それがこれだけで実現できるようになりました(現状Chrome75以上だけですが)
<img src="celebration.jpg" loading="lazy" alt="..." /> <iframe src="video-player.html" loading="lazy"></iframe>
詳しい使い方やデモはこちらにあります。 Native image lazy-loading for the web!
goa v2で一番単純なBasicAuthを実装するまで
- design.goにBasicAuthSecurityを追加します。
package design // The convention consists of naming the design // package "design" import ( . "github.com/goadesign/goa/design/apidsl" ) var _ = API("area", func() { // API defines the microservice endpoint and Title("area API") // other global properties. There should be one Description("A simple goa service") // and exactly one API definition appearing in Scheme("http") // the design. Host("localhost:8080") }) var BasicAuth = BasicAuthSecurity("BasicAuth", func() { //←これ追加 Description("Use client ID and client secret to authenticate") })
- Resource(同じくdesign.goの中)に↑で宣言したBasicAuthを追加します。
var _ = Resource("point", func() { // Resources group related API endpoints BasePath("/points") // together. They map to REST resources for REST DefaultMedia(PointMedia) // services. Security(BasicAuth) //←これ追加 ・・・・
goagen bootstrapした後のmain.goに追記します。
package main import ( "github.com/goadesign/goa" "github.com/goadesign/goa/middleware" "github.com/goadesign/goa/middleware/security/basicauth" "github.com/hryktrd/goaTest/app" ) func main() { // Create service service := goa.New("area") app.UseBasicAuthMiddleware(service, basicauth.New("admin", "password")) //←これ追加
これでgo buildしてpostmanなどで適当なAPIにアクセスすると
{"id":"SqvDzqfu","code":"basic_auth_failed","status":401,"detail":"Authentication failed"}
みたいに返ってきて認証失敗するようになりました。 上で
basicauth.New
したときの ID/PW = admin/passwordを設定してみるとAPIにアクセスできるようになりました。
いつのまにかにfont-awesomeのアイコンではなくprimeiconsになっていた
PrimeNG 7.1.0でのお話です。
今までPrimeNGでは例えばメニューのアイコンを置きたい場合
label: 'New', icon: 'fa fa-plus', command: (event) => {
みたいにしてfont awesomeのアイコンを使用するのが主流でしたが、いつのまにかにprimeiconsでまかなえるようになっていたようです。
npm install primeicons --save
した上で、例えば上記みたいにメニューにアイコンを表示したい場合は
label: 'New', icon: 'pi pi-plus', command: (event) => {
としてやればfont awesomeのときと同じようにできます。
使えるアイコン一覧はこちら
ていうか、なぜ未だに公式のサンプルでfont awesomeを使う方のサンプルを記述しているのでしょうか。。。
プロジェクトを作成し、ngModelを使おうとして早速エラーが出たとき
新しく作ったプロジェクトで早速あたりのコンポーネントを使用して、[(ngModel)]="hogehoge"などとした時に早速ブラウザのコンソールに
“Can't bind to 'ngModel' since it isn't a known property of 'input' ” error
というエラーが出たという方
app.module.tsで
import {FormsModule} from '@angular/forms';
して、
imports: [ ..... FormsModule // ←これ追加 ],
したら直ると思います。(久々の新規プロジェクト作成で10分くらいハマった・・・