最近 Apple の審査でリジェクトされた時の対応方法

Guideline 2.1 - Information Needed

We have started the review of your app, but we are not able to continue because we need access to a video that demonstrates Audio on background your app in use on a physical iOS device.

Please ensure the video you provide shows a physical iOS device (not a simulator).

Next Steps

To help us proceed with the review of your app, please provide us with a link to a demo video in the App Review Information section of iTunes Connect and reply to this message in Resolution Center.

To provide a link to a demo video:

  • Log in to iTunes Connect
  • Click on "My Apps"
  • Select your app
  • Click on the app version on the left side of the screen
  • Scroll down to "App Review Information"
  • Provide demo video access details in the "Notes" section
  • Click "Save"
  • Once you've completed all changes, click the "Submit for Review" button at the top of the App Version Information page.

Once this information is available, we can continue with the review of your app.

審査の時に、テスト方法を説明する動画をアップロードする必要がある。動画をスマホで撮影して、添付しました。

Guideline 2.1 - Information Needed


We have started the review of your app, but we are not able to continue because we need additional information about your app.

Next Steps

To help us proceed with the review of your app, please provide detailed information to the following questions. The more information you can provide upfront, the sooner we can complete your review.

  • What is the significant difference between this app and your ●●● app?

Once you reply to this message in Resolution Center with the requested information, we can proceed with your review.

自分が公開している、名前が似ているアプリがある場合、違いを説明する必要がありました。実際に中身が異なっていても、名前が似ているだけで必要です。

Guideline 2.2 - Performance - Beta Testing


Your app contains references to test, trial, demo, beta, pre-release or other incomplete content.

Specifically, the naming indicates that this is beta.

“●●● β”


Next Steps

To resolve this issue, please remove all references to "demo," "trial," "beta," or "test" in your app description, app icon, screenshots, previews, release notes, and binary. If you would like to conduct a beta trial for your app, you may wish to review the TestFlight Beta Testing Guide.

アプリタイトルに β の文字がある場合は、消す必要があります。

CloudFront の CNAME は、サブドメインであれば実質2個まで同じドメインが指定可能

CloudFront の CNAME の設定は、全CloudFrontでユニークにしなければなりませんが、
CloudFront の CNAME には、ワイルドカードでも指定できるため、
*.testdomain.testwww.testdomain.test で 2つ登録すれば、実質、同じドメインが指定できます。

ただし、Route53上では、Aレコードではなく、CNAMEレコードで登録する必要があります。

これが出来れば、何か移行作業時に助かるかもしれません。

一般提供された CloudFront lambda@Edge で、Basic認証を設定する

preview と 仕様が変わっています。

nodejsは6.10を選ぶ必要があり、headers の構造も変わっていました。
Cloudfrontのarn指定に、lambda を version がなければいけないのも、なかなかめんどくさい変更です。
あとは、role の設定も面倒ですが、一旦書きません。

"use strict";

exports.handle = (event, context, callback) => {
    
    const Allows = {
        "users": "users",
    };

    const request = event.Records[0].cf.request;
    const headers = request.headers;

    const authorization = headers.authorization || headers.Authorization;

    if (authorization) {

        const enc = authorization[0].value.split(" ")[1];
        const userPassword = new Buffer(enc, 'base64').toString();

        for (let key in Allows) {
            let val = Allows[key];
            if (`${key}:${val}` === userPassword) {
                callback(null, request);
                return;
            }
        }
    }

    const response = {
        status: '401',
        statusDescription: 'Authorization Required',
        httpVersion: request.httpVersion,
        headers: {
            "www-authenticate": [{key:"WWW-Authenticate", value: 'Basic realm="Enter username and password."'}],
            "content-type": [{ key: "Content-Type", value: "text/plain; charset=utf-8" }],
        },
        body: "401 Authorization Required",
    };

    callback(null, response);
};

CloudFront にCloudFront を入れるときの注意点

CloudFront は、Request header に含まれる Hostの値が、CloudFrontに設定されたCNAMEと一致していないとレスポンスを返さない。
CloudFrontに設定されたCNAMEは、すべてのCloudFrontで2つ同じCNAMEを設定できない。

Behavior の Forward Headers は、Host を含めた場合は、ブラウザがリクエストしたHostがそのままOriginまで到達するが、
含めない場合は、Originに設定したHostのまま、Originへリクエストする。
実はここがあまり書かれていないのでわかりにくい。

この条件を満たすため、
CloufFrontのOriginにCloudFront を含めるような場合は、Behavior の Forward Headers は、All が設定できず、Whitelist を使わなければならない。
たとえば All の代わりに、User-Agent、Authorization などを設定することになるはずです。

mysql5.7 の 注意点 (< 5.7.11)

RDS 上で mysql 5.7 を使うときに覚えておかなければならないことは一つで、

default_password_lifetime
この値が、5.7.10 以下の時、360 になっています。

SELECT @@default_password_lifetime;

つまり360日で期限切れになりログインできなくなります。RDS 作成時のマスターパスワードであっても同じです。

Your password has expired. To log in you must change it using a client that supports expired passwords.

万が一、期限が切れてしまった場合は、
RDSのマスターパスワードをリセットし、mysqlにログイン。

ALTER USER user_name PASSWORD EXPIRE NEVER;

これで password_lifetime = 0 となり、ログインできます。

5.7.11以降のバージョンは、default_password_lifetime = 0 になっているので、問題はありません。

Route53 で Domain name status code がブランクな場合

jp や、io のドメインで確認しましたが、Domain name status code がブランクのままになるドメインがあります。
whois コマンドで、

[状態] Active

となっていれば、気にせず安心して良いと、AWSに回答いただきました。

CloudFront lambda@Edge で、Basic認証を設定する

S3+CloudFrontなど、サーバーレスでBasic認証をかける方法を何年も検討していたのですが、CloudFrontにLambdaを割り当てられるようになり、試したところ実装することがで来ました。

まだPreview版ですが、フォームから申請することで、数日で許可が得られます。

イベントの種類

まず仕組みとしては、以下4つのイベントに、機能が絞られたLambda@Edgeを割り当てることがで来ます。

Viewer リクエス

リクエスト時に呼ばれるイベント

Viewer レスポンス

レスポンス時に呼ばれるイベント

Origin リクエス

オリジナルデータにリクエストが到達した時に呼ばれるイベント

Origin レスポンス

オリジナルデータからのレスポンス時に呼ばれるイベント

リクエストとレスポンスの違い

レスポンスの場合
  • レスポンスヘッダーの参照
  • レスポンスヘッダーの書き換え
  • レスポンスを返す

こちらはあまり使うことを検討していないので、もしかしたら他の機能もあるかもしれません。

リクエストの場合
  • リクエストヘッダーの参照
  • Origin へのリクエストヘッダーの書き換え
  • Origin の切り替え
  • Origin にアクセスせず、その場でレスポンスを返す

status が callback に渡した連想配列に含まれているかどうかで、Originへアクセスするかどうかの挙動が変わるようです。



今回は Viewer リクエストを使います。
Basic 認証の仕様に合わせてヘッダーを返すことで、Basic認証を実装することがで来ました。

exports.handler = (event, context, callback) => {
    
    const Allows = {
        "users": "users",
    };
    
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const authorization = headers.authorization || headers.Authorization;

    if (authorization) {
        
        const enc = authorization[0].split(" ")[1];
        const userPassword = new Buffer(enc, 'base64').toString();
        
        for (var key in Allows) {
            var val = Allows[key];
            if (`${key}:${val}` === userPassword) {
                callback(null, request);
                return
            }
        }
    }

    const response = {
        status: '401',
        statusDescription: 'Authorization Required',
        httpVersion: request.httpVersion,
        headers: {
          "WWW-Authenticate": ['Basic realm="Enter username and password."'],
          "Content-Type": ["text/plain; charset=utf-8"],
        },
        body: "401 Authorization Required",
    };

    callback(null, response);
};

event のサンプル

※ event に入ってくるデータは、以下のように小文字でくることもあれば、大文字始まりで受け取る場合もあるので、どちらにも対応しなければならないようです。

{
  "host":["dummy.dayo"],
  "authorization":["Basic dXNlcnM6dXNlcnM="],
  "cache-control":["no-cache"],
  "user-agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"],
  "accept":["*/*"],
  "accept-encoding":["gzip, deflate, sdch, br"],
  "accept-language":["ja,en;q=0.8,en-US;q=0.6"]
}


ログがでないと言うことと、Lambda@Edgeを割り当てたときだけでなく、コードを書き換えたあとも、数分待ち時間があることで、確認が大変でした。

ElasticBeanstalk+CloudFrontのときにも、このBasic認証の設定が活躍するのでうれしいです。

unching-star.hatenablog.jp