diff --git a/CHANGELOG.md b/CHANGELOG.md index 3622ba4c5e..94a0a4a64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,14 @@ styled with Tailwind CSS by default. You can opt-out of Tailwind CSS with the `- flag (the Tailwind CSS classes are kept in the generated components as reference for future styling). +## 1.7.10 (2023-11-03) + +### Bug fixes + * [phx.new] – fix `CoreComponents.flash` generating incorrect id's causing flash messages to fail to be closed when clicked + +### Enhancements + * Support dynamic port for `Endpoint.url/0` + ## 1.7.9 (2023-10-11) ### Bug fixes diff --git a/assets/package-lock.json b/assets/package-lock.json index 0368d1e6b5..de09e0970a 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix", - "version": "1.7.9", + "version": "1.7.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "1.7.9", + "version": "1.7.10", "license": "MIT", "devDependencies": { "@babel/cli": "7.14.3", @@ -86,12 +86,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -137,13 +138,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -237,9 +239,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -255,25 +257,25 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -379,30 +381,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -444,13 +446,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -458,9 +460,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1392,33 +1394,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1427,13 +1429,13 @@ } }, "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1514,13 +1516,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nicolo-ribaudo/chokidar-2": { @@ -9300,12 +9302,13 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -9338,13 +9341,14 @@ } }, "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, @@ -9420,9 +9424,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -9435,22 +9439,22 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -9544,24 +9548,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -9594,20 +9598,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { @@ -10334,42 +10338,42 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -10431,13 +10435,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@nicolo-ribaudo/chokidar-2": { diff --git a/assets/package.json b/assets/package.json index a5e88c4dfc..9f7a89ee19 100644 --- a/assets/package.json +++ b/assets/package.json @@ -1,6 +1,6 @@ { "name": "phoenix", - "version": "1.7.9", + "version": "1.7.10", "description": "The official JavaScript client for the Phoenix web framework.", "license": "MIT", "main": "./assets/js/phoenix/index.js", diff --git a/guides/authentication/api_authentication.md b/guides/authentication/api_authentication.md new file mode 100644 index 0000000000..3d59d439f4 --- /dev/null +++ b/guides/authentication/api_authentication.md @@ -0,0 +1,142 @@ +# API Authentication + +> **Requirement**: This guide expects that you have gone through the [`mix phx.gen.auth`](mix_phx_gen_auth.html) guide. + +This guide shows how to add API authentication on top of `mix phx.gen.auth`. Since the authentication generator already includes a token table, we use it to store API tokens too, following the best security practices. + +We will break this guide in two parts: augmenting the context and the plug implementation. We will assume that the following `mix phx.gen.auth` command was executed: + +``` +$ mix phx.gen.auth Accounts User users +``` + +If you ran something else, it should be trivial to adapt the names. + +## Adding API functions to the context + +Our authentication system will require two functions. One to create the API token and another to verify it. Open up `lib/my_app/accounts.ex` and add these two new functions: + +```elixir + ## API + + @doc """ + Creates a new api token for a user. + + The token returned must be saved somewhere safe. + This token cannot be recovered from the database. + """ + def create_user_api_token(user) do + {encoded_token, user_token} = UserToken.build_email_token(user, "api-token") + Repo.insert!(user_token) + encoded_token + end + + @doc """ + Fetches the user by API token. + """ + def fetch_user_by_api_token(token) do + with {:ok, query} <- UserToken.verify_email_token_query(token, "api-token"), + %User{} = user <- Repo.one(query) do + {:ok, user} + else + _ -> :error + end + end +``` + +The new functions use the existing `UserToken` functionality to store a new type of token called "api-token". Because this is an email token, if the user changes their email, the tokens will be expired. + +Also notice we called the second function `fetch_user_by_api_token`, instead of `get_user_by_api_token`. Because we want to render different status codes in our API, depending if a user was found or not, we return `{:ok, user}` or `:error`. Elixir's convention is to call these functions `fetch_*`, instead of `get_*` which would usually return `nil` instead of tuples. + +To make sure our new functions work, let's write tests. Open up `test/my_app/accounts_test.exs` and add this new describe block: + +```elixir + describe "create_user_api_token/1 and fetch_user_by_api_token/1" do + test "creates and fetches by token" do + user = user_fixture() + token = Accounts.create_user_api_token(user) + assert Accounts.fetch_user_by_api_token(token) == {:ok, user} + assert Accounts.fetch_user_by_api_token("invalid") == :error + end + end +``` + +If you run the tests, they will actually fail. Something similar to this: + +```elixir +1) test create_user_api_token/1 and fetch_user_by_api_token/1 creates and verify token (Demo.AccountsTest) + test/demo/accounts_test.exs:21 + ** (FunctionClauseError) no function clause matching in Demo.Accounts.UserToken.days_for_context/1 + + The following arguments were given to Demo.Accounts.UserToken.days_for_context/1: + + # 1 + "api-token" + + Attempted function clauses (showing 2 out of 2): + + defp days_for_context("confirm") + defp days_for_context("reset_password") + + code: assert Accounts.verify_api_token(token) == {:ok, user} + stacktrace: + (demo 0.1.0) lib/demo/accounts/user_token.ex:129: Demo.Accounts.UserToken.days_for_context/1 + (demo 0.1.0) lib/demo/accounts/user_token.ex:114: Demo.Accounts.UserToken.verify_email_token_query/2 + (demo 0.1.0) lib/demo/accounts.ex:301: Demo.Accounts.verify_api_token/1 + test/demo/accounts_test.exs:24: (test) +``` + +If you prefer, try looking at the error and fixing it yourself. The explanation will come next. + +The `UserToken` module expects us to declare the validity of each token and we haven't defined one for "api-token". The length is going to depend on your application and how sensitive it is in terms of security. For this example, let's say the token is valid for 365 days. + +Open up `lib/my_app/accounts/user_token.ex`, find where `defp days_for_context` is defined, and add a new clause, like this: + +```elixir + defp days_for_context("api-token"), do: 365 + defp days_for_context("confirm"), do: @confirm_validity_in_days + defp days_for_context("reset_password"), do: @reset_password_validity_in_days +``` + +Now tests should pass and we are ready to move forward! + +## API authentication plug + +The last part is to add authentication to our API. + +When we ran `mix phx.gen.auth`, it generated a `MyAppWeb.UserAuth` module with several plugs, which are small functions that receive the `conn` and customize our request/response life-cycle. Open up `lib/my_app_web/user_auth.ex` and add this new function: + +```elixir +def fetch_api_user(conn, _opts) do + with ["Bearer " <> token] <- get_req_header("authorization"), + {:ok, user} <- Accounts.fetch_user_by_api_token(token) do + assign(conn, :current_user, user) + else + _ -> + conn + |> send_resp(:unauthorized, "No access for you") + |> halt() + end +end +``` + +Our function receives the connection and checks if the "authorization" header has been set with "Bearer TOKEN", where "TOKEN" is the value returned by `Accounts.create_user_api_token/1`. In case the token is not valid or there is no such user, we abort the request. + +Finally, we need to add this `plug` to our pipeline. Open up `lib/my_app_web/router.ex` and you will find a pipeline for API. Let's add our new plug under it, like this: + +```elixir + pipeline :api do + plug :accepts, ["json"] + plug :fetch_api_user + end +``` + +Now you are ready to receive and validate API requests. Feel free to open up `test/my_app_web/user_auth_test.exs` and write your own test. You can use the tests for other plugs as templates! + +## Your turn + +The overall API authentication flow will depend on your application. + +If you want to use this token in a JavaScript client, you will need to slightly alter the `UserSessionController` to invoke `Accounts.create_user_api_token/1` and return a JSON response and include the token returned it. + +If you want to provide APIs for 3rd-party users, you will need to allow them to create tokens, and show the result of `Accounts.create_user_api_token/1` to them. They must save these tokens somewhere safe and include them as part of their requests using the "authorization" header. diff --git a/guides/cheatsheets/router.cheatmd b/guides/cheatsheets/router.cheatmd new file mode 100644 index 0000000000..1f0af13e72 --- /dev/null +++ b/guides/cheatsheets/router.cheatmd @@ -0,0 +1,83 @@ +# Routing cheatsheet + +> Those need to be declared in the correct router module and scope. + +A quick reference to the common routing features' syntax. For an exhaustive overview, refer to the [routing guides](routing.md). + +## Routing declaration +{: .col-2} + +### Single route + +```elixir +get "/users", UserController, :index +patch "/users/:id", UserController, :update +``` +```elixir +# generated routes +~p"/users" +~p"/users/9" # user_id is 9 +``` +Also accepts `put`, `patch`, `options`, `delete` and `head`. + +### Resources + +#### Simple + +```elixir +resources "/users", UserController +``` +Generates `:index`, `:edit`, `:new`, `:show`, `:create`, `:update` and `:delete`. + +#### Options + +```elixir +resources "/users", UserController, only: [:show] +resources "/users", UserController, except: [:create, :delete] +resources "/users", UserController, as: :person # ~p"/person" +``` + +#### Nested + +```elixir +resources "/users", UserController do + resources "/posts", PostController +end +``` +```elixir +# generated routes +~p"/users/3/posts" # user_id is 3 +~p"/users/3/posts/17" # user_id is 3 and post_id = 17 +``` +For more info check the [resources docs.](routing-1.html#resources) + +### Scopes + +#### Simple +```elixir +scope "/admin", HelloWeb.Admin do + pipe_through :browser + + resources "/users", UserController +end +``` +```elixir +# generated path helpers +~p"/admin/users" +``` + +#### Nested +```elixir +scope "/api", HelloWeb.Api, as: :api do + pipe_through :api + + scope "/v1", V1, as: :v1 do + resources "/users", UserController + end +end +``` +```elixir +# generated path helpers +~p"/api/v1/users" +``` +For more info check the [scoped routes](routing.md#scoped-routes) docs. diff --git a/guides/components.md b/guides/components.md index 9e9e59a3e5..e6431b9b90 100644 --- a/guides/components.md +++ b/guides/components.md @@ -52,7 +52,7 @@ defmodule HelloWeb.HelloHTML do end ``` -We declared the attributes we accept via `attr` provided by `Phoenix.Component`, then we defined our `greet/1` function which returns the HEEx template. +We declared the attributes we accept via `attr` provided by `Phoenix.Component`, then we defined our `greet/1` function which returns the HEEx template. Next we need to update `show.html.heex`: @@ -62,9 +62,9 @@ Next we need to update `show.html.heex`: ``` -When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. +When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. -Since templates are embedded inside the `HelloHTML` module, we were able to invoke the view function simply as `<.greet messenger="..." />`. +Since templates are embedded inside the `HelloHTML` module, we were able to invoke the view function simply as `<.greet messenger="..." />`. If the component was defined elsewhere, we can also type ``. @@ -225,7 +225,7 @@ When we load the page, we should be rendering the admin layout without the heade At this point, you may be wondering, why does Phoenix have two layouts? -First of all, it gives us flexibility. In practice, we will hardly have multiple root layouts, as they often contain only HTML headers. This allows us to focus on different application layouts with only the parts that changes between them. Second of all, Phoenix ships with a feature called LiveView, which allows us to build rich and real-time user experiences with server-rendered HTML. LiveView is capable of dynamically changing the contents of the page, but it only ever changes the app layout, never the root layout. We will learn about LiveView in future guides. +First of all, it gives us flexibility. In practice, we will hardly have multiple root layouts, as they often contain only HTML headers. This allows us to focus on different application layouts with only the parts that changes between them. Second of all, Phoenix ships with a feature called LiveView, which allows us to build rich and real-time user experiences with server-rendered HTML. LiveView is capable of dynamically changing the contents of the page, but it only ever changes the app layout, never the root layout. Check out [the LiveView documentation](https://hexdocs.pm/phoenix_live_view) to learn more. ## CoreComponents diff --git a/guides/contexts.md b/guides/contexts.md index 1cd73bbd7b..d7ff0d9faa 100644 --- a/guides/contexts.md +++ b/guides/contexts.md @@ -405,7 +405,7 @@ $ mix ecto.migrate Now that we have a `Catalog.Product` schema and a join table to associate products and categories, we're nearly ready to start wiring up our new features. Before we dive in, we first need real categories to select in our web UI. Let's quickly seed some new categories in the application. Add the following code to your seeds file in `priv/repo/seeds.exs`: ```elixir -for title <- ["Home Improvement", "Power Tools", "Gardening", "Books"] do +for title <- ["Home Improvement", "Power Tools", "Gardening", "Books", "Education"] do {:ok, _} = Hello.Catalog.create_category(%{title: title}) end ``` @@ -923,7 +923,7 @@ defmodule HelloWeb.CartHTML do use HelloWeb, :html alias Hello.ShoppingCart - + embed_templates "cart_html/*" def currency_to_str(%Decimal{} = val), do: "$#{Decimal.round(val, 2)}" @@ -1348,6 +1348,16 @@ Great work! ## FAQ +### How do I structure code inside contexts? + +You may wonder how to organize the code inside contexts. For example, should you define a module for changesets (such as ProductChangesets) and another module for queries (such as ProductQueries)? + +One important benefit of contexts is that this decision does not matter much. The context is your public API, the other modules are private. Contexts isolate these modules into small groups so the surface area of your application is the context and not _all of your code_. + +So while you and your team could establish patterns for organizing these private modules, it is also our opinion it is completely fine for them to be different. The major focus should be on how the contexts are defined and how they interact with each other (and with your web application). + +Think about it as a well-kept neighbourhood. Your contexts are houses, you want to keep them well-preserved, well-connected, etc. Inside the houses, they may all be a little bit different, and that's fine. + ### Returning Ecto structures from context APIs As we explored the context API, you might have wondered: diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md index f34d434f22..87c88d1163 100644 --- a/guides/deployment/heroku.md +++ b/guides/deployment/heroku.md @@ -176,17 +176,6 @@ config :hello, Hello.Repo, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") ``` - - -Then open up your `config/runtime.exs` (formerly `config/prod.secret.exs`) and uncomment the `# ssl: true,` line in your repository configuration. It will look like this: - -```elixir -config :hello, Hello.Repo, - ssl: true, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") -``` - Finally, if you plan on using websockets, then we will need to decrease the timeout for the websocket transport in `lib/hello_web/endpoint.ex`. If you do not plan on using websockets, then leaving it set to false is fine. You can find further explanation of the options available at the [documentation](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration). ```elixir diff --git a/guides/real_time/presence.md b/guides/real_time/presence.md index 2686962111..106ea41775 100644 --- a/guides/real_time/presence.md +++ b/guides/real_time/presence.md @@ -132,7 +132,7 @@ With token authentication, you should access `socket.assigns.user_id`, set in `U ## Usage With LiveView -Whilst Phoenix does ship with a JavaScript API for dealing with presence, it is also possible to extend the `HelloWeb.Presence` module to support LiveView. +Whilst Phoenix does ship with a JavaScript API for dealing with presence, it is also possible to extend the `HelloWeb.Presence` module to support [LiveView](https://hexdocs.pm/phoenix_live_view). One thing to keep in mind when dealing with LiveView, is that each LiveView is a stateful process, so if we keep the presence state in the LiveView, each LiveView process will contain the full list of online users in memory. Instead, we can keep track of the online users within the `Presence` process, and pass separate events to the LiveView, which can use a stream to update the online list. diff --git a/guides/request_lifecycle.md b/guides/request_lifecycle.md index 79a1e88a73..aee1b99e00 100644 --- a/guides/request_lifecycle.md +++ b/guides/request_lifecycle.md @@ -223,7 +223,7 @@ At this moment, you may be thinking this can be a lot of steps to simply render * view - the view handles the structured data from the controller and converts it to a presentation to be shown to users. Views are often named after the content format they are rendering. -Let's do a quick recap and how the last three components work together by adding another page. +Let's do a quick recap on how the last three components work together by adding another page. ## Another new page diff --git a/installer/mix.exs b/installer/mix.exs index 2073f8d6f7..7bc7e50ea1 100644 --- a/installer/mix.exs +++ b/installer/mix.exs @@ -6,7 +6,7 @@ end defmodule Phx.New.MixProject do use Mix.Project - @version "1.7.9" + @version "1.7.10" @scm_url "https://github.com/phoenixframework/phoenix" # If the elixir requirement is updated, we need to update: diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs index a09cd87ca5..ec749456ca 100644 --- a/installer/templates/phx_single/mix.exs +++ b/installer/templates/phx_single/mix.exs @@ -45,7 +45,7 @@ defmodule <%= @app_module %>.MixProject do {:phoenix_live_view, "~> 0.20.1"}, {:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %> {:phoenix_live_dashboard, "~> 0.8.2"},<% end %><%= if @javascript do %> - {:esbuild, "~> 0.7", runtime: Mix.env() == :dev},<% end %><%= if @css do %> + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %> {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},<% end %><%= if @mailer do %> {:swoosh, "~> 1.3"}, {:finch, "~> 0.13"},<% end %> diff --git a/installer/templates/phx_static/favicon.ico b/installer/templates/phx_static/favicon.ico index 73de524aaa..7f372bfc21 100644 Binary files a/installer/templates/phx_static/favicon.ico and b/installer/templates/phx_static/favicon.ico differ diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs index 7d750b1ae4..f193ce99c2 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs @@ -43,7 +43,7 @@ defmodule <%= @web_namespace %>.MixProject do {:phoenix_live_view, "~> 0.20.1"}, {:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %> {:phoenix_live_dashboard, "~> 0.8.2"},<% end %><%= if @javascript do %> - {:esbuild, "~> 0.7", runtime: Mix.env() == :dev},<% end %><%= if @css do %> + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %> {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},<% end %> {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"},<%= if @gettext do %> diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex index 85586fecaf..6f7d48c674 100644 --- a/installer/templates/phx_web/components/core_components.ex +++ b/installer/templates/phx_web/components/core_components.ex @@ -97,7 +97,7 @@ defmodule <%= @web_namespace %>.CoreComponents do <.flash kind={:info} flash={@flash} /> <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! """ - attr :id, :string, default: nil, doc: "the optional id of flash container" + attr :id, :string, doc: "the optional id of flash container" attr :flash, :map, default: %{}, doc: "the map of flash messages to display" attr :title, :string, default: nil attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" diff --git a/integration_test/mix.exs b/integration_test/mix.exs index 8719a2aaed..3fdffbfd91 100644 --- a/integration_test/mix.exs +++ b/integration_test/mix.exs @@ -34,7 +34,7 @@ defmodule Phoenix.Integration.MixProject do {:phx_new, path: "../installer"}, {:phoenix, path: "..", override: true}, {:phoenix_ecto, "~> 4.4"}, - {:esbuild, "~> 0.7", runtime: false}, + {:esbuild, "~> 0.8", runtime: false}, {:ecto_sql, "~> 3.10"}, {:postgrex, ">= 0.0.0"}, {:myxql, ">= 0.0.0"}, diff --git a/integration_test/mix.lock b/integration_test/mix.lock index c242649216..b993d27cf4 100644 --- a/integration_test/mix.lock +++ b/integration_test/mix.lock @@ -1,7 +1,7 @@ %{ "argon2_elixir": {:hex, :argon2_elixir, "3.1.0", "4135e0a1b4ff800d42c85aa663e068efa3cb356297189b5b65caa992db8ec8cf", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c08feae0ee0292165d1b945003363c7cd8523d002e0483c627dfca930291dd73"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, - "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, + "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.7", "77de20ac77f0e53f20ca82c563520af0237c301a1ec3ab3bc598e8a96c7ee5d9", [:mix], [{:elixir_make, "~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2768b28bf3c2b4f788c995576b39b8cb5d47eb788526d93bd52206c1d8bf4b75"}, "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, @@ -14,7 +14,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.10.3", "82ce316a8727f1daec397a9932b1a20130ea1ac33c3257b78eded1d3f45ae9b3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.9", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "b4fa32d09f5e5c05d3401ade3dd4416e3c7072d5117c150cb4adeea72760fb93"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, - "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"}, + "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "exqlite": {:hex, :exqlite, "0.13.14", "acd8b58c2245c6aa611262a887509c6aa862a05bfeb174faf348375bd9fc7edb", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "e81cd9b811e70a43b8d2d4ee76d3ce57ff349890ec4182f8f5223ead38ac4996"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, diff --git a/lib/mix/tasks/phx.gen.cert.ex b/lib/mix/tasks/phx.gen.cert.ex index 84b6d1e1b4..d3ada0f183 100644 --- a/lib/mix/tasks/phx.gen.cert.ex +++ b/lib/mix/tasks/phx.gen.cert.ex @@ -49,7 +49,9 @@ defmodule Mix.Tasks.Phx.Gen.Cert do @doc false def run(all_args) do if Mix.Project.umbrella?() do - Mix.raise("mix phx.gen.cert must be invoked from within your *_web application root directory") + Mix.raise( + "mix phx.gen.cert must be invoked from within your *_web application root directory" + ) end {opts, args} = @@ -255,8 +257,8 @@ defmodule Mix.Tasks.Phx.Gen.Cert do issuer: rdn(common_name), validity: validity( - notBefore: {:utcTime, '#{not_before}000000Z'}, - notAfter: {:utcTime, '#{not_after}000000Z'} + notBefore: {:utcTime, ~c"#{not_before}000000Z"}, + notAfter: {:utcTime, ~c"#{not_after}000000Z"} ), subject: rdn(common_name), subjectPublicKeyInfo: diff --git a/lib/mix/tasks/phx.routes.ex b/lib/mix/tasks/phx.routes.ex index d12348c4a7..de95b1683d 100644 --- a/lib/mix/tasks/phx.routes.ex +++ b/lib/mix/tasks/phx.routes.ex @@ -182,7 +182,7 @@ defmodule Mix.Tasks.Phx.Routes do end) case function_infos do - {_, line, _, _, _} -> line + {_, anno, _, _, _} -> :erl_anno.line(anno) nil -> nil end end diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex index 0c7490f939..b76ebe7f6b 100644 --- a/lib/phoenix/endpoint.ex +++ b/lib/phoenix/endpoint.ex @@ -251,6 +251,9 @@ defmodule Phoenix.Endpoint do * for handling paths and URLs: `c:struct_url/0`, `c:url/0`, `c:path/1`, `c:static_url/0`,`c:static_path/1`, and `c:static_integrity/1` + * for gethering runtime information about the address and port the + endpoint is running on: `c:server_info/1` + * for broadcasting to channels: `c:broadcast/3`, `c:broadcast!/3`, `c:broadcast_from/4`, `c:broadcast_from!/4`, `c:local_broadcast/3`, and `c:local_broadcast_from/4` @@ -342,6 +345,15 @@ defmodule Phoenix.Endpoint do """ @callback host() :: String.t() + # Server information + + @doc """ + Returns the address and port that the server is running on + """ + @callback server_info(Plug.Conn.scheme()) :: + {:ok, {:inet.ip_address(), :inet.port_number()} | :inet.returned_non_ip_address()} + | {:error, term()} + # Channels @doc """ @@ -605,6 +617,11 @@ defmodule Phoenix.Endpoint do &Phoenix.Endpoint.Supervisor.static_lookup(&1, path) ) end + + @doc """ + Returns the address and port that the server is running on + """ + def server_info(scheme), do: config(:adapter).server_info(__MODULE__, scheme) end end diff --git a/lib/phoenix/endpoint/cowboy2_adapter.ex b/lib/phoenix/endpoint/cowboy2_adapter.ex index 84117a9c7a..2356e26bd1 100644 --- a/lib/phoenix/endpoint/cowboy2_adapter.ex +++ b/lib/phoenix/endpoint/cowboy2_adapter.ex @@ -80,7 +80,7 @@ defmodule Phoenix.Endpoint.Cowboy2Adapter do Application.ensure_all_started(:ssl) end - ref = Module.concat(endpoint, scheme |> Atom.to_string() |> String.upcase()) + ref = make_ref(endpoint, scheme) plug = if code_reloader? do @@ -137,4 +137,16 @@ defmodule Phoenix.Endpoint.Cowboy2Adapter do defp port_to_integer({:system, env_var}), do: port_to_integer(System.get_env(env_var)) defp port_to_integer(port) when is_binary(port), do: String.to_integer(port) defp port_to_integer(port) when is_integer(port), do: port + + def server_info(endpoint, scheme) do + make_ref(endpoint, scheme) + |> :ranch.get_addr() + |> then(&{:ok, &1}) + rescue + e -> {:error, e.message} + end + + defp make_ref(endpoint, scheme) do + Module.concat(endpoint, scheme |> Atom.to_string() |> String.upcase()) + end end diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex index d1e66a113f..98d4f55c78 100644 --- a/lib/phoenix/endpoint/supervisor.ex +++ b/lib/phoenix/endpoint/supervisor.ex @@ -113,7 +113,7 @@ defmodule Phoenix.Endpoint.Supervisor do end defp socket_children(endpoint, fun) do - for {_, socket, opts} <- Enum.uniq_by(endpoint.__sockets__, &elem(&1, 1)), + for {_, socket, opts} <- Enum.uniq_by(endpoint.__sockets__(), &elem(&1, 1)), spec = apply_or_ignore(socket, fun, [[endpoint: endpoint] ++ opts]), spec != :ignore do spec @@ -137,7 +137,7 @@ defmodule Phoenix.Endpoint.Supervisor do defp server_children(mod, config, server?) do cond do server? -> - adapter = config[:adapter] || Phoenix.Endpoint.Cowboy2Adapter + adapter = config[:adapter] adapter.child_specs(mod, config) config[:http] || config[:https] -> @@ -196,6 +196,7 @@ defmodule Phoenix.Endpoint.Supervisor do render_errors: [view: render_errors(module), accepts: ~w(html), layout: false], # Runtime config + adapter: Phoenix.Endpoint.Cowboy2Adapter, cache_static_manifest: nil, check_origin: true, http: false, diff --git a/lib/phoenix/transports/websocket.ex b/lib/phoenix/transports/websocket.ex index 16be35bb54..f04cc4350d 100644 --- a/lib/phoenix/transports/websocket.ex +++ b/lib/phoenix/transports/websocket.ex @@ -57,9 +57,13 @@ defmodule Phoenix.Transports.WebSocket do case handler.connect(config) do {:ok, arg} -> - conn - |> WebSockAdapter.upgrade(handler, arg, opts) - |> halt() + try do + conn + |> WebSockAdapter.upgrade(handler, arg, opts) + |> halt() + rescue + e in WebSockAdapter.UpgradeError -> send_resp(conn, 400, e.message) + end :error -> send_resp(conn, 403, "") diff --git a/mix.exs b/mix.exs index d7dba14ee9..c4987c9c7c 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule Phoenix.MixProject do end end - @version "1.7.9" + @version "1.7.10" @scm_url "https://github.com/phoenixframework/phoenix" # If the elixir requirement is updated, we need to make the installer @@ -104,7 +104,7 @@ defmodule Phoenix.MixProject do {:mint_web_socket, "~> 1.0.0", only: :test}, # Dev dependencies - {:esbuild, "~> 0.7", only: :dev} + {:esbuild, "~> 0.8", only: :dev} ] end @@ -156,6 +156,7 @@ defmodule Phoenix.MixProject do "guides/telemetry.md", "guides/asset_management.md", "guides/authentication/mix_phx_gen_auth.md", + "guides/authentication/api_authentication.md", "guides/real_time/channels.md", "guides/real_time/presence.md", "guides/testing/testing.md", @@ -171,6 +172,7 @@ defmodule Phoenix.MixProject do "guides/howto/file_uploads.md", "guides/howto/using_ssl.md", "guides/howto/writing_a_channels_client.md", + "guides/cheatsheets/router.cheatmd", "CHANGELOG.md" ] end @@ -183,6 +185,7 @@ defmodule Phoenix.MixProject do "Real-time": ~r/guides\/real_time\/.?/, Testing: ~r/guides\/testing\/.?/, Deployment: ~r/guides\/deployment\/.?/, + Cheatsheets: ~r/guides\/cheatsheets\/.?/, "How-to's": ~r/guides\/howto\/.?/ ] end diff --git a/mix.lock b/mix.lock index 217b37233f..77ecea94ff 100644 --- a/mix.lock +++ b/mix.lock @@ -11,7 +11,7 @@ "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"}, "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, "hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, @@ -36,6 +36,6 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, - "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, } diff --git a/package.json b/package.json index 574d59732d..9a5feb4ac7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "phoenix", - "version": "1.7.9", + "version": "1.7.10", "description": "The official JavaScript client for the Phoenix web framework.", "license": "MIT", "module": "./priv/static/phoenix.mjs", diff --git a/priv/static/favicon.ico b/priv/static/favicon.ico index 73de524aaa..7f372bfc21 100644 Binary files a/priv/static/favicon.ico and b/priv/static/favicon.ico differ diff --git a/priv/templates/phx.gen.auth/context_functions.ex b/priv/templates/phx.gen.auth/context_functions.ex index 2f9b1bed3a..cb340fed19 100644 --- a/priv/templates/phx.gen.auth/context_functions.ex +++ b/priv/templates/phx.gen.auth/context_functions.ex @@ -146,7 +146,7 @@ Ecto.Multi.new() |> Ecto.Multi.update(:<%= schema.singular %>, changeset) - |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [context])) + |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [context])) end @doc ~S""" @@ -199,7 +199,7 @@ Ecto.Multi.new() |> Ecto.Multi.update(:<%= schema.singular %>, changeset) - |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all)) + |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all)) |> Repo.transaction() |> case do {:ok, %{<%= schema.singular %>: <%= schema.singular %>}} -> {:ok, <%= schema.singular %>} @@ -230,7 +230,7 @@ Deletes the signed token with the given context. """ def delete_<%= schema.singular %>_session_token(token) do - Repo.delete_all(<%= inspect schema.alias %>Token.token_and_context_query(token, "session")) + Repo.delete_all(<%= inspect schema.alias %>Token.by_token_and_context_query(token, "session")) :ok end @@ -278,7 +278,7 @@ defp confirm_<%= schema.singular %>_multi(<%= schema.singular %>) do Ecto.Multi.new() |> Ecto.Multi.update(:<%= schema.singular %>, <%= inspect schema.alias %>.confirm_changeset(<%= schema.singular %>)) - |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, ["confirm"])) + |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, ["confirm"])) end ## Reset password @@ -335,7 +335,7 @@ def reset_<%= schema.singular %>_password(<%= schema.singular %>, attrs) do Ecto.Multi.new() |> Ecto.Multi.update(:<%= schema.singular %>, <%= inspect schema.alias %>.password_changeset(<%= schema.singular %>, attrs)) - |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all)) + |> Ecto.Multi.delete_all(:tokens, <%= inspect schema.alias %>Token.by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all)) |> Repo.transaction() |> case do {:ok, %{<%= schema.singular %>: <%= schema.singular %>}} -> {:ok, <%= schema.singular %>} diff --git a/priv/templates/phx.gen.auth/schema_token.ex b/priv/templates/phx.gen.auth/schema_token.ex index 71bdc954ac..b0b946867b 100644 --- a/priv/templates/phx.gen.auth/schema_token.ex +++ b/priv/templates/phx.gen.auth/schema_token.ex @@ -58,7 +58,7 @@ defmodule <%= inspect schema.module %>Token do """ def verify_session_token_query(token) do query = - from token in token_and_context_query(token, "session"), + from token in by_token_and_context_query(token, "session"), join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>), where: token.inserted_at > ago(@session_validity_in_days, "day"), select: <%= schema.singular %> @@ -116,7 +116,7 @@ defmodule <%= inspect schema.module %>Token do days = days_for_context(context) query = - from token in token_and_context_query(hashed_token, context), + from token in by_token_and_context_query(hashed_token, context), join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>), where: token.inserted_at > ago(^days, "day") and token.sent_to == <%= schema.singular %>.email, select: <%= schema.singular %> @@ -151,7 +151,7 @@ defmodule <%= inspect schema.module %>Token do hashed_token = :crypto.hash(@hash_algorithm, decoded_token) query = - from token in token_and_context_query(hashed_token, context), + from token in by_token_and_context_query(hashed_token, context), where: token.inserted_at > ago(@change_email_validity_in_days, "day") {:ok, query} @@ -164,18 +164,18 @@ defmodule <%= inspect schema.module %>Token do @doc """ Returns the token struct for the given token value and context. """ - def token_and_context_query(token, context) do + def by_token_and_context_query(token, context) do from <%= inspect schema.alias %>Token, where: [token: ^token, context: ^context] end @doc """ Gets all tokens for the given <%= schema.singular %> for the given contexts. """ - def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all) do + def by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all) do from t in <%= inspect schema.alias %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id end - def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [_ | _] = contexts) do + def by_<%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [_ | _] = contexts) do from t in <%= inspect schema.alias %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id and t.context in ^contexts end end diff --git a/priv/templates/phx.gen.live/core_components.ex b/priv/templates/phx.gen.live/core_components.ex index 85586fecaf..6f7d48c674 100644 --- a/priv/templates/phx.gen.live/core_components.ex +++ b/priv/templates/phx.gen.live/core_components.ex @@ -97,7 +97,7 @@ defmodule <%= @web_namespace %>.CoreComponents do <.flash kind={:info} flash={@flash} /> <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! """ - attr :id, :string, default: nil, doc: "the optional id of flash container" + attr :id, :string, doc: "the optional id of flash container" attr :flash, :map, default: %{}, doc: "the map of flash messages to display" attr :title, :string, default: nil attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" diff --git a/test/mix/tasks/phx.gen.cert_test.exs b/test/mix/tasks/phx.gen.cert_test.exs index 06d5a30c90..abc4483ca8 100644 --- a/test/mix/tasks/phx.gen.cert_test.exs +++ b/test/mix/tasks/phx.gen.cert_test.exs @@ -56,7 +56,7 @@ defmodule Mix.Tasks.Phx.CertTest do # We don't actually verify the server cert contents, we just check that # the client and server are able to complete the TLS handshake - assert {:ok, client} = :ssl.connect('localhost', port, [verify: :verify_none], @timeout) + assert {:ok, client} = :ssl.connect(~c"localhost", port, [verify: :verify_none], @timeout) :ssl.close(client) :ssl.close(server) end) diff --git a/test/phoenix/endpoint/endpoint_test.exs b/test/phoenix/endpoint/endpoint_test.exs index beb946a994..720fb6467a 100644 --- a/test/phoenix/endpoint/endpoint_test.exs +++ b/test/phoenix/endpoint/endpoint_test.exs @@ -227,6 +227,22 @@ defmodule Phoenix.Endpoint.EndpointTest do assert StaticEndpoint.static_path("/phoenix.png") =~ "/static/phoenix.png" end + @tag :capture_log + test "can find the running address and port for an endpoint" do + Application.put_env(:phoenix, __MODULE__.AddressEndpoint, + http: [ip: {127, 0, 0, 1}, port: 0], + server: true + ) + + defmodule AddressEndpoint do + use Phoenix.Endpoint, otp_app: :phoenix + end + + AddressEndpoint.start_link() + assert {:ok, {{127, 0, 0, 1}, port}} = AddressEndpoint.server_info(:http) + assert is_integer(port) + end + test "injects pubsub broadcast with configured server" do Endpoint.subscribe("sometopic") some = spawn fn -> :ok end diff --git a/test/phoenix/integration/websocket_socket_test.exs b/test/phoenix/integration/websocket_socket_test.exs index 5bc4c5599d..aa249f4138 100644 --- a/test/phoenix/integration/websocket_socket_test.exs +++ b/test/phoenix/integration/websocket_socket_test.exs @@ -4,7 +4,7 @@ defmodule Phoenix.Integration.WebSocketTest do use ExUnit.Case, async: true import ExUnit.CaptureLog - alias Phoenix.Integration.WebsocketClient + alias Phoenix.Integration.{HTTPClient, WebsocketClient} alias __MODULE__.Endpoint @moduletag :capture_log @@ -104,6 +104,14 @@ defmodule Phoenix.Integration.WebSocketTest do :ok end + test "handles invalid upgrade requests" do + capture_log(fn -> + path = String.replace_prefix(@path, "ws", "http") + assert {:ok, %{body: body, status: 400}} = HTTPClient.request(:get, path, %{}) + assert body =~ "'connection' header must contain 'upgrade'" + end) + end + test "refuses unallowed origins" do capture_log(fn -> headers = [{"origin", "https://example.com"}]